New upstream version 6.9.2
authorStuart Prescott <stuart@debian.org>
Sun, 31 Aug 2025 19:54:41 +0000 (05:54 +1000)
committerStuart Prescott <stuart@debian.org>
Sun, 31 Aug 2025 19:54:41 +0000 (05:54 +1000)
729 files changed:
.QT-ENTERPRISE-LICENSE-AGREEMENT [deleted file]
.gitreview [new file with mode: 0644]
README.pyside6.md
README.pyside6_addons.md
README.pyside6_essentials.md
README.pyside6_examples.md
SECURITY.md [new file with mode: 0644]
build_history/blacklist.txt
build_scripts/config.py
build_scripts/main.py
build_scripts/platforms/unix.py
build_scripts/platforms/windows_desktop.py
build_scripts/qfp_tool.py
build_scripts/utils.py
build_scripts/wheel_files.py
coin/dependencies.yaml
coin/fetch_libclang_arm64.ps1 [new file with mode: 0644]
coin/fetch_libclang_arm64.sh
coin/instructions/common_environment.yaml
coin/instructions/execute_desktop_instructions.yaml
coin/instructions/execute_test_instructions.yaml
coin/instructions/relocate_pyside.yaml
coin/instructions_utils.py
coin/module_config.yaml
create_wheels.py
doc/changelogs/changes-6.8.3 [new file with mode: 0644]
doc/changelogs/changes-6.9.0 [new file with mode: 0644]
doc/changelogs/changes-6.9.1 [new file with mode: 0644]
doc/changelogs/changes-6.9.2 [new file with mode: 0644]
examples/charts/donutbreakdown/donutbreakdown.py
examples/charts/dynamicspline/chart.py
examples/charts/lineandbar/lineandbar.py
examples/demos/documentviewer/doc/imageviewer.py.rstinc [new file with mode: 0644]
examples/demos/documentviewer/documentviewer.pyproject
examples/demos/documentviewer/imageviewer/imageviewer.py [new file with mode: 0644]
examples/demos/documentviewer/pdfviewer/pdfviewer.py
examples/demos/documentviewer/viewerfactory.py
examples/graphs/2d/graphsaudio/GraphsAudio/Main.qml [new file with mode: 0644]
examples/graphs/2d/graphsaudio/GraphsAudio/qmldir [new file with mode: 0644]
examples/graphs/2d/graphsaudio/doc/graphsaudio.rst [new file with mode: 0644]
examples/graphs/2d/graphsaudio/doc/graphsaudio.webp [new file with mode: 0644]
examples/graphs/2d/graphsaudio/graphsaudio.pyproject [new file with mode: 0644]
examples/graphs/2d/graphsaudio/main.py [new file with mode: 0644]
examples/graphs/3d/widgetgraphgallery/highlightseries.py
examples/graphs/3d/widgetgraphgallery/scatterdatamodifier.py
examples/graphs/3d/widgetgraphgallery/scattergraph.py
examples/gui/analogclock/main.py
examples/multimedia/camera/camera.py
examples/webenginequick/nanobrowser/ApplicationRoot.qml
examples/webenginequick/nanobrowser/BrowserWindow.qml
examples/webenginequick/nanobrowser/DownloadView.qml
examples/webenginequick/nanobrowser/FindBar.qml
examples/webenginequick/nanobrowser/quicknanobrowser.py
examples/webenginewidgets/simplebrowser/browser.py
examples/webenginewidgets/simplebrowser/main.py
examples/webenginewidgets/simplebrowser/ui_webauthdialog.py [new file with mode: 0644]
examples/webenginewidgets/simplebrowser/webauthdialog.py [new file with mode: 0644]
examples/webenginewidgets/simplebrowser/webauthdialog.ui [new file with mode: 0644]
examples/webenginewidgets/simplebrowser/webpopupwindow.py
examples/webenginewidgets/simplebrowser/webview.py
examples/widgets/itemviews/stardelegate/starrating.py
examples/widgets/painting/basicdrawing/basicdrawing.py
examples/widgets/tutorials/addressbook/addressbook.pyproject [deleted file]
examples/widgets/tutorials/addressbook/doc/addressbook.png [deleted file]
examples/widgets/tutorials/addressbook/doc/addressbook.rst [deleted file]
examples/widgets/tutorials/addressbook/part1.py [deleted file]
examples/widgets/tutorials/addressbook/part2.py [deleted file]
examples/widgets/tutorials/addressbook/part3.py [deleted file]
examples/widgets/tutorials/addressbook/part4.py [deleted file]
examples/widgets/tutorials/addressbook/part5.py [deleted file]
examples/widgets/tutorials/addressbook/part6.py [deleted file]
examples/widgets/tutorials/addressbook/part7.py [deleted file]
examples/widgets/tutorials/cannon/t11.py
examples/widgets/tutorials/cannon/t12.py
examples/widgets/tutorials/cannon/t13.py
examples/widgets/tutorials/cannon/t14.py
examples/xml/dombookmarks/dombookmarks.py
requirements-coin.txt
requirements.txt
sources/pyside-tools/deploy.py
sources/pyside-tools/deploy_lib/android/android_utilities.py
sources/pyside-tools/deploy_lib/config.py
sources/pyside-tools/deploy_lib/default.spec
sources/pyside-tools/deploy_lib/dependency_util.py
sources/pyside-tools/metaobjectdump.py
sources/pyside-tools/project.py
sources/pyside-tools/project_lib/__init__.py
sources/pyside-tools/project_lib/newproject.py
sources/pyside-tools/project_lib/project_data.py
sources/pyside-tools/project_lib/pyproject_json.py [new file with mode: 0644]
sources/pyside-tools/project_lib/pyproject_parse_result.py [new file with mode: 0644]
sources/pyside-tools/project_lib/pyproject_toml.py [new file with mode: 0644]
sources/pyside-tools/project_lib/utils.py
sources/pyside-tools/pyside_tool.py
sources/pyside-tools/requirements-android.txt
sources/pyside6/.cmake.conf
sources/pyside6/CMakeLists.txt
sources/pyside6/PySide6/CMakeLists.txt
sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml
sources/pyside6/PySide6/QtAsyncio/events.py
sources/pyside6/PySide6/QtAsyncio/tasks.py
sources/pyside6/PySide6/QtCore/CMakeLists.txt
sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp
sources/pyside6/PySide6/QtCore/typesystem_core_common.xml
sources/pyside6/PySide6/QtGraphs/CMakeLists.txt
sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml
sources/pyside6/PySide6/QtGui/CMakeLists.txt
sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml
sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml
sources/pyside6/PySide6/QtNetwork/typesystem_network.xml
sources/pyside6/PySide6/QtNetworkAuth/CMakeLists.txt
sources/pyside6/PySide6/QtNetworkAuth/typesystem_networkauth.xml
sources/pyside6/PySide6/QtOpenGL/typesystem_opengl.xml
sources/pyside6/PySide6/QtOpenGL/typesystem_opengl_modifications1_0.xml
sources/pyside6/PySide6/QtPrintSupport/typesystem_printsupport_common.xml
sources/pyside6/PySide6/QtQml/typesystem_qml.xml
sources/pyside6/PySide6/QtQuick/typesystem_quick.xml
sources/pyside6/PySide6/QtRemoteObjects/CMakeLists.txt
sources/pyside6/PySide6/QtRemoteObjects/typesystem_remoteobjects.xml
sources/pyside6/PySide6/QtSerialBus/typesystem_serialbus.xml
sources/pyside6/PySide6/QtTest/typesystem_test.xml
sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt
sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml
sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt
sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml
sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml
sources/pyside6/PySide6/__init__.py.in
sources/pyside6/PySide6/_config.py.in
sources/pyside6/PySide6/doc/qtcore.rst
sources/pyside6/PySide6/doc/qtqml.rst
sources/pyside6/PySide6/glue/qtcore.cpp
sources/pyside6/PySide6/glue/qtgui.cpp
sources/pyside6/PySide6/glue/qtnetwork.cpp
sources/pyside6/PySide6/glue/qtquick.cpp
sources/pyside6/PySide6/glue/qtremoteobjects.cpp [new file with mode: 0644]
sources/pyside6/PySide6/glue/qttest.cpp
sources/pyside6/PySide6/glue/qtwidgets.cpp
sources/pyside6/PySide6/templates/common.xml
sources/pyside6/PySide6/templates/core_common.xml
sources/pyside6/PySide6/templates/datavisualization_common.xml
sources/pyside6/PySide6/templates/gui_common.xml
sources/pyside6/PySide6/templates/opengl_common.xml [deleted file]
sources/pyside6/PySide6/templates/widgets_common.xml
sources/pyside6/cmake/Macros/PySideModules.cmake
sources/pyside6/cmake/PySideHelpers.cmake
sources/pyside6/cmake/PySideSetup.cmake
sources/pyside6/doc/building_from_source/linux.rst
sources/pyside6/doc/building_from_source/macOS.rst
sources/pyside6/doc/building_from_source/windows.rst
sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst
sources/pyside6/doc/developer/add_port_example.rst
sources/pyside6/doc/developer/extras.rst
sources/pyside6/doc/developer/fix_type_hints.rst [new file with mode: 0644]
sources/pyside6/doc/developer/index.rst
sources/pyside6/doc/developer/limited_api.rst
sources/pyside6/doc/developer/remoteobjects.md [new file with mode: 0644]
sources/pyside6/doc/faq/typesoffiles.rst
sources/pyside6/doc/tools/pyside-project.rst
sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst
sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py
sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py
sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py
sources/pyside6/doc/tutorials/expenses/main_snake_prop.py
sources/pyside6/doc/tutorials/expenses/steps/01-expenses.py
sources/pyside6/doc/tutorials/finance_manager/part3/part3.md
sources/pyside6/doc/tutorials/modelviewprogramming/qlistview-dnd.py
sources/pyside6/doc/tutorials/modelviewprogramming/stringlistmodel.py
sources/pyside6/doc/tutorials/portingguide/chapter1/createdb.py
sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.py
sources/pyside6/doc/tutorials/portingguide/chapter2/createdb.py
sources/pyside6/doc/tutorials/portingguide/chapter2/main.py
sources/pyside6/doc/tutorials/portingguide/chapter3/bookdelegate-old.py
sources/pyside6/doc/tutorials/portingguide/chapter3/bookdelegate.py
sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.py
sources/pyside6/doc/tutorials/portingguide/chapter3/chapter3.pyproject [new file with mode: 0644]
sources/pyside6/doc/tutorials/portingguide/chapter3/createdb.py
sources/pyside6/doc/tutorials/portingguide/chapter3/main.py
sources/pyside6/doc/tutorials/portingguide/chapter3/rc_books.py [new file with mode: 0644]
sources/pyside6/doc/tutorials/portingguide/chapter3/ui_bookwindow.py [new file with mode: 0644]
sources/pyside6/doc/tutorials/portingguide/hello_world_ex.py
sources/pyside6/libpyside/CMakeLists.txt
sources/pyside6/libpyside/class_property.cpp
sources/pyside6/libpyside/dynamicqmetaobject.cpp
sources/pyside6/libpyside/dynamicqmetaobject.h
sources/pyside6/libpyside/dynamicslot.cpp
sources/pyside6/libpyside/dynamicslot_p.h
sources/pyside6/libpyside/feature_select.cpp
sources/pyside6/libpyside/pyside.cpp
sources/pyside6/libpyside/pyside_numpy.h
sources/pyside6/libpyside/pysideclassdecorator_p.h
sources/pyside6/libpyside/pysideclassinfo.cpp
sources/pyside6/libpyside/pysideclassinfo.h
sources/pyside6/libpyside/pysideclassinfo_p.h
sources/pyside6/libpyside/pysidelogging_p.h
sources/pyside6/libpyside/pysidemetafunction.cpp
sources/pyside6/libpyside/pysidemetafunction.h
sources/pyside6/libpyside/pysideproperty.cpp
sources/pyside6/libpyside/pysideproperty.h
sources/pyside6/libpyside/pysideproperty_p.h
sources/pyside6/libpyside/pysideqenum.cpp
sources/pyside6/libpyside/pysideqenum.h
sources/pyside6/libpyside/pysideqhash.h
sources/pyside6/libpyside/pysideqmetatype.h
sources/pyside6/libpyside/pysideqslotobject_p.h
sources/pyside6/libpyside/pysidesignal.cpp
sources/pyside6/libpyside/pysidesignal.h
sources/pyside6/libpyside/pysidesignal_p.h
sources/pyside6/libpyside/pysideslot.cpp
sources/pyside6/libpyside/pysideutils.h
sources/pyside6/libpyside/pysideweakref.cpp
sources/pyside6/libpyside/pysideweakref.h
sources/pyside6/libpyside/qobjectconnect.cpp
sources/pyside6/libpyside/qobjectconnect.h
sources/pyside6/libpyside/signalmanager.cpp
sources/pyside6/libpyside/signalmanager.h
sources/pyside6/libpysideqml/CMakeLists.txt
sources/pyside6/libpysideqml/pysideqml.cpp
sources/pyside6/libpysideqml/pysideqmlattached.cpp
sources/pyside6/libpysideqml/pysideqmlextended.cpp
sources/pyside6/libpysideqml/pysideqmlforeign.cpp
sources/pyside6/libpysideqml/pysideqmllistproperty.cpp
sources/pyside6/libpysideqml/pysideqmlmetacallerror.cpp
sources/pyside6/libpysideqml/pysideqmlnamedelement.cpp
sources/pyside6/libpysideqml/pysideqmlregistertype.cpp
sources/pyside6/libpysideqml/pysideqmlregistertype_p.h
sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp
sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h
sources/pyside6/libpysideqml/pysideqmluncreatable.cpp
sources/pyside6/libpysideqml/pysideqmluncreatable.h
sources/pyside6/libpysideremoteobjects/CMakeLists.txt [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysidecapsulemethod.cpp [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysidecapsulemethod_p.h [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysidedynamicclass.cpp [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysidedynamicclass_p.h [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysidedynamiccommon.cpp [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysidedynamiccommon_p.h [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysidedynamicenum.cpp [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysidedynamicenum_p.h [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysidedynamicpod.cpp [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysidedynamicpod_p.h [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysideremoteobjects.h [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysiderephandler.cpp [new file with mode: 0644]
sources/pyside6/libpysideremoteobjects/pysiderephandler_p.h [new file with mode: 0644]
sources/pyside6/plugins/designer/CMakeLists.txt
sources/pyside6/plugins/designer/designercustomwidgets.cpp
sources/pyside6/plugins/designer/designercustomwidgets.h
sources/pyside6/plugins/uitools/customwidget.cpp
sources/pyside6/plugins/uitools/customwidget.h
sources/pyside6/plugins/uitools/customwidgets.h
sources/pyside6/qtexampleicons/CMakeLists.txt
sources/pyside6/tests/QtBluetooth/localdevice.py
sources/pyside6/tests/QtBluetooth/lowenergy_characteristics.py
sources/pyside6/tests/QtCore/CMakeLists.txt
sources/pyside6/tests/QtCore/attr_cache_py3k.py
sources/pyside6/tests/QtCore/bug_1313.py
sources/pyside6/tests/QtCore/bug_324.py
sources/pyside6/tests/QtCore/bug_332.py
sources/pyside6/tests/QtCore/bug_706.py
sources/pyside6/tests/QtCore/bug_835.py
sources/pyside6/tests/QtCore/destroysignal_test.py
sources/pyside6/tests/QtCore/errormessages_with_features_test.py
sources/pyside6/tests/QtCore/max_signals.py
sources/pyside6/tests/QtCore/multiple_feature_test.py
sources/pyside6/tests/QtCore/python_conversion.py
sources/pyside6/tests/QtCore/qanimationgroup_test.py
sources/pyside6/tests/QtCore/qbytearray_operator_iadd_test.py
sources/pyside6/tests/QtCore/qiodevice_buffered_read_test.py
sources/pyside6/tests/QtCore/qiopipe_test.py
sources/pyside6/tests/QtCore/qlocale_test.py
sources/pyside6/tests/QtCore/qlockfile_test.py
sources/pyside6/tests/QtCore/qmessage_logger_test.py [new file with mode: 0644]
sources/pyside6/tests/QtCore/qobject_children_segfault_test.py
sources/pyside6/tests/QtCore/qobject_destructor.py
sources/pyside6/tests/QtCore/qobject_inherits_test.py
sources/pyside6/tests/QtCore/qobject_parent_test.py
sources/pyside6/tests/QtCore/qobject_property_test.py
sources/pyside6/tests/QtCore/qproperty_decorator.py
sources/pyside6/tests/QtCore/qrandomgenerator_test.py
sources/pyside6/tests/QtCore/qsharedmemory_client.py
sources/pyside6/tests/QtCore/snake_prop_feature_test.py
sources/pyside6/tests/QtCore/versioninfo_test.py
sources/pyside6/tests/QtGui/CMakeLists.txt
sources/pyside6/tests/QtGui/bug_660.py
sources/pyside6/tests/QtGui/bug_PYSIDE-344.py
sources/pyside6/tests/QtGui/event_filter_test.py
sources/pyside6/tests/QtGui/pyside_reload_test.py [deleted file]
sources/pyside6/tests/QtGui/qfont_test.py
sources/pyside6/tests/QtGui/qicon_test.py
sources/pyside6/tests/QtGui/qpaintengine_test.py [new file with mode: 0644]
sources/pyside6/tests/QtGui/qpixmap_constructor.py
sources/pyside6/tests/QtHelp/help_test.py
sources/pyside6/tests/QtLocation/location.py
sources/pyside6/tests/QtMultimediaWidgets/qmultimediawidgets.py
sources/pyside6/tests/QtPositioning/positioning.py
sources/pyside6/tests/QtQml/CMakeLists.txt
sources/pyside6/tests/QtQml/groupedproperty.py
sources/pyside6/tests/QtQml/qquickitem_grabToImage.py
sources/pyside6/tests/QtQml/registerattached.py
sources/pyside6/tests/QtQml/registerextended.py
sources/pyside6/tests/QtQml/registerparserstatus.py
sources/pyside6/tests/QtQml/registerqmlfile.py
sources/pyside6/tests/QtQml/registersingletontype.py
sources/pyside6/tests/QtQml/signal_types.py
sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/tst_quicktestmainwithsetup.py
sources/pyside6/tests/QtRemoteObjects/CMakeLists.txt
sources/pyside6/tests/QtRemoteObjects/cpp_interop/CMakeLists.txt [new file with mode: 0644]
sources/pyside6/tests/QtRemoteObjects/cpp_interop/cpp_interop.cpp [new file with mode: 0644]
sources/pyside6/tests/QtRemoteObjects/cpp_interop/cpp_interop_test.py [new file with mode: 0644]
sources/pyside6/tests/QtRemoteObjects/dynamic_types_test.py [new file with mode: 0644]
sources/pyside6/tests/QtRemoteObjects/integration_test.py [new file with mode: 0644]
sources/pyside6/tests/QtRemoteObjects/repfile_test.py [new file with mode: 0644]
sources/pyside6/tests/QtRemoteObjects/simple.rep [new file with mode: 0644]
sources/pyside6/tests/QtRemoteObjects/test_shared.py [new file with mode: 0644]
sources/pyside6/tests/QtSerialBus/CMakeLists.txt
sources/pyside6/tests/QtSerialBus/QtSerialBus.pyproject [new file with mode: 0644]
sources/pyside6/tests/QtSerialBus/serialbus.py [new file with mode: 0644]
sources/pyside6/tests/QtSerialBus/test.dbc [new file with mode: 0644]
sources/pyside6/tests/QtTest/CMakeLists.txt
sources/pyside6/tests/QtTest/touchevent_test.py
sources/pyside6/tests/QtUiTools/CMakeLists.txt
sources/pyside6/tests/QtUiTools/bug_913.py
sources/pyside6/tests/QtUiTools/bug_958.py
sources/pyside6/tests/QtUiTools/loadUiType_test.py
sources/pyside6/tests/QtWidgets/CMakeLists.txt
sources/pyside6/tests/QtWidgets/action_clear.py
sources/pyside6/tests/QtWidgets/bug_1002.py
sources/pyside6/tests/QtWidgets/bug_1006.py
sources/pyside6/tests/QtWidgets/bug_1048.py
sources/pyside6/tests/QtWidgets/bug_243.py
sources/pyside6/tests/QtWidgets/bug_338.py
sources/pyside6/tests/QtWidgets/bug_433.py
sources/pyside6/tests/QtWidgets/bug_525.py
sources/pyside6/tests/QtWidgets/bug_546.py
sources/pyside6/tests/QtWidgets/bug_576.py
sources/pyside6/tests/QtWidgets/bug_640.py
sources/pyside6/tests/QtWidgets/bug_653.py
sources/pyside6/tests/QtWidgets/bug_668.py
sources/pyside6/tests/QtWidgets/bug_674.py
sources/pyside6/tests/QtWidgets/bug_693.py
sources/pyside6/tests/QtWidgets/bug_750.py
sources/pyside6/tests/QtWidgets/bug_862.py
sources/pyside6/tests/QtWidgets/bug_919.py
sources/pyside6/tests/QtWidgets/bug_941.py
sources/pyside6/tests/QtWidgets/bug_967.py
sources/pyside6/tests/QtWidgets/bug_988.py
sources/pyside6/tests/QtWidgets/bug_998.py
sources/pyside6/tests/QtWidgets/pyside3069.py [new file with mode: 0644]
sources/pyside6/tests/QtWidgets/pyside_reload_test.py [new file with mode: 0644]
sources/pyside6/tests/QtWidgets/qapp_issue_585.py
sources/pyside6/tests/QtWidgets/qapp_test.py
sources/pyside6/tests/QtWidgets/qdialog_test.py
sources/pyside6/tests/QtWidgets/qfontdialog_test.py
sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py
sources/pyside6/tests/QtWidgets/qinputdialog_get_test.py
sources/pyside6/tests/QtWidgets/qlayout_ref_test.py
sources/pyside6/tests/QtWidgets/qlayout_test.py
sources/pyside6/tests/QtWidgets/qlistwidgetitem_test.py
sources/pyside6/tests/QtWidgets/qmainwindow_test.py
sources/pyside6/tests/QtWidgets/qmenu_test.py
sources/pyside6/tests/QtWidgets/qpicture_test.py
sources/pyside6/tests/QtWidgets/qstyle_test.py
sources/pyside6/tests/QtWidgets/qtextedit_test.py
sources/pyside6/tests/QtWidgets/qtreewidget_test.py
sources/pyside6/tests/init_paths.py
sources/pyside6/tests/manually/sizebench.py
sources/pyside6/tests/pysidetest/CMakeLists.txt
sources/pyside6/tests/pysidetest/bug_1016.py
sources/pyside6/tests/pysidetest/enum_test.py
sources/pyside6/tests/pysidetest/iterable_test.py
sources/pyside6/tests/pysidetest/mypy_correctness_test.py
sources/pyside6/tests/pysidetest/notify_id.py
sources/pyside6/tests/pysidetest/signal_slot_warning.py
sources/pyside6/tests/pysidetest/signal_tp_descr_get_test.py
sources/pyside6/tests/pysidetest/signalemissionfrompython_test.py
sources/pyside6/tests/pysidetest/testobject.cpp
sources/pyside6/tests/pysidetest/testobject.h
sources/pyside6/tests/registry/existence_test.py
sources/pyside6/tests/signals/CMakeLists.txt
sources/pyside6/tests/signals/disconnect_test.py
sources/pyside6/tests/signals/nonqobject_receivers_test.py [new file with mode: 0644]
sources/pyside6/tests/signals/ref03_test.py
sources/pyside6/tests/signals/slot_reference_count_test.py
sources/pyside6/tests/tools/metaobjectdump/test_metaobjectdump.py
sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py
sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py
sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/.pyproject [deleted file]
sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/Drumpad.pyproject [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/pyproject.toml [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/example_project/example_project.pyproject [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/example_project/folder/label_in_folder.py [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/example_project/main.py [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/example_project/mainwindow.py [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/example_project/pyproject.toml [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject_button.py [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/existing_pyproject_toml.pyproject [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/main.py [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/pyproject.toml [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/zzz.py [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/invalid_pyproject.pyproject [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/main.py [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/pyproject.toml [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/valid_pyproject.pyproject [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/common_file.py [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/expected_pyproject.toml [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/file1.py [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/file2.py [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/project1.pyproject [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/project2.pyproject [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py
sources/pyside6/tests/tools/pyside6-qml/test_pyside6_qml.py
sources/pyside6/tests/util/helper/basicpyslotcase.py
sources/pyside6/tests/util/helper/docmodifier.py
sources/pyside6/tests/util/helper/usesqapplication.py
sources/pyside6/tests/util/processtimer.py [deleted file]
sources/pyside6/tests/util/test_processtimer.py [deleted file]
sources/shiboken6/.cmake.conf
sources/shiboken6/ApiExtractor/CMakeLists.txt
sources/shiboken6/ApiExtractor/abstractmetaargument.cpp
sources/shiboken6/ApiExtractor/abstractmetaargument.h
sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp
sources/shiboken6/ApiExtractor/abstractmetabuilder.h
sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h
sources/shiboken6/ApiExtractor/abstractmetaenum.cpp
sources/shiboken6/ApiExtractor/abstractmetaenum.h
sources/shiboken6/ApiExtractor/abstractmetafield.cpp
sources/shiboken6/ApiExtractor/abstractmetafield.h
sources/shiboken6/ApiExtractor/abstractmetafunction.cpp
sources/shiboken6/ApiExtractor/abstractmetafunction.h
sources/shiboken6/ApiExtractor/abstractmetalang.cpp
sources/shiboken6/ApiExtractor/abstractmetalang.h
sources/shiboken6/ApiExtractor/abstractmetalang_enums.h
sources/shiboken6/ApiExtractor/abstractmetalang_typedefs.h
sources/shiboken6/ApiExtractor/abstractmetatype.cpp
sources/shiboken6/ApiExtractor/abstractmetatype.h
sources/shiboken6/ApiExtractor/addedfunction.cpp
sources/shiboken6/ApiExtractor/addedfunction.h
sources/shiboken6/ApiExtractor/addedfunction_p.h
sources/shiboken6/ApiExtractor/anystringview_helpers.cpp
sources/shiboken6/ApiExtractor/anystringview_helpers.h
sources/shiboken6/ApiExtractor/apiextractor.cpp
sources/shiboken6/ApiExtractor/apiextractor.h
sources/shiboken6/ApiExtractor/apiextractorflags.h
sources/shiboken6/ApiExtractor/apiextractorresult.h
sources/shiboken6/ApiExtractor/arraytypeentry.h
sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp
sources/shiboken6/ApiExtractor/clangparser/clangbuilder.h
sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.cpp
sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp
sources/shiboken6/ApiExtractor/clangparser/clangparser.h
sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp
sources/shiboken6/ApiExtractor/clangparser/clangutils.h
sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp
sources/shiboken6/ApiExtractor/clangparser/compilersupport.h
sources/shiboken6/ApiExtractor/classdocumentation.cpp
sources/shiboken6/ApiExtractor/classdocumentation.h
sources/shiboken6/ApiExtractor/codesnip.cpp
sources/shiboken6/ApiExtractor/codesnip.h
sources/shiboken6/ApiExtractor/codesniphelpers.cpp
sources/shiboken6/ApiExtractor/codesniphelpers.h
sources/shiboken6/ApiExtractor/complextypeentry.h
sources/shiboken6/ApiExtractor/conditionalstreamreader.cpp
sources/shiboken6/ApiExtractor/conditionalstreamreader.h
sources/shiboken6/ApiExtractor/configurabletypeentry.h
sources/shiboken6/ApiExtractor/cpptypeentry.h [new file with mode: 0644]
sources/shiboken6/ApiExtractor/customconversion.h
sources/shiboken6/ApiExtractor/customconversion_typedefs.h
sources/shiboken6/ApiExtractor/customtypenentry.h
sources/shiboken6/ApiExtractor/debughelpers_p.h
sources/shiboken6/ApiExtractor/dependency.h
sources/shiboken6/ApiExtractor/docparser.cpp
sources/shiboken6/ApiExtractor/docparser.h
sources/shiboken6/ApiExtractor/documentation.cpp
sources/shiboken6/ApiExtractor/documentation.h
sources/shiboken6/ApiExtractor/documentation_enums.h
sources/shiboken6/ApiExtractor/dotview.cpp
sources/shiboken6/ApiExtractor/dotview.h
sources/shiboken6/ApiExtractor/doxygenparser.cpp
sources/shiboken6/ApiExtractor/exception.h
sources/shiboken6/ApiExtractor/filecache.cpp [new file with mode: 0644]
sources/shiboken6/ApiExtractor/filecache.h [new file with mode: 0644]
sources/shiboken6/ApiExtractor/fileout.cpp
sources/shiboken6/ApiExtractor/flagstypeentry.h
sources/shiboken6/ApiExtractor/graph.h
sources/shiboken6/ApiExtractor/header_paths.h
sources/shiboken6/ApiExtractor/include.cpp
sources/shiboken6/ApiExtractor/include.h
sources/shiboken6/ApiExtractor/messages.cpp
sources/shiboken6/ApiExtractor/messages.h
sources/shiboken6/ApiExtractor/modifications.cpp
sources/shiboken6/ApiExtractor/modifications.h
sources/shiboken6/ApiExtractor/modifications_typedefs.h
sources/shiboken6/ApiExtractor/optionsparser.cpp
sources/shiboken6/ApiExtractor/optionsparser.h
sources/shiboken6/ApiExtractor/parser/codemodel.cpp
sources/shiboken6/ApiExtractor/parser/codemodel.h
sources/shiboken6/ApiExtractor/parser/codemodel_fwd.h
sources/shiboken6/ApiExtractor/parser/enumvalue.cpp
sources/shiboken6/ApiExtractor/parser/enumvalue.h
sources/shiboken6/ApiExtractor/parser/typeinfo.cpp
sources/shiboken6/ApiExtractor/parser/typeinfo.h
sources/shiboken6/ApiExtractor/predefined_templates.cpp
sources/shiboken6/ApiExtractor/predefined_templates.h
sources/shiboken6/ApiExtractor/primitivetypeentry.h
sources/shiboken6/ApiExtractor/propertyspec.cpp
sources/shiboken6/ApiExtractor/propertyspec.h
sources/shiboken6/ApiExtractor/pymethoddefentry.cpp
sources/shiboken6/ApiExtractor/pymethoddefentry.h
sources/shiboken6/ApiExtractor/pythontypeentry.h
sources/shiboken6/ApiExtractor/qtcompat.h
sources/shiboken6/ApiExtractor/qtdocparser.cpp
sources/shiboken6/ApiExtractor/reporthandler.cpp
sources/shiboken6/ApiExtractor/reporthandler.h
sources/shiboken6/ApiExtractor/smartpointertypeentry.h
sources/shiboken6/ApiExtractor/sourcelocation.cpp
sources/shiboken6/ApiExtractor/sourcelocation.h
sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.cpp
sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.h
sources/shiboken6/ApiExtractor/tests/testabstractmetatype.cpp
sources/shiboken6/ApiExtractor/tests/testabstractmetatype.h
sources/shiboken6/ApiExtractor/tests/testaddfunction.cpp
sources/shiboken6/ApiExtractor/tests/testaddfunction.h
sources/shiboken6/ApiExtractor/tests/testarrayargument.cpp
sources/shiboken6/ApiExtractor/tests/testarrayargument.h
sources/shiboken6/ApiExtractor/tests/testcodeinjection.cpp
sources/shiboken6/ApiExtractor/tests/testcodeinjection.h
sources/shiboken6/ApiExtractor/tests/testcontainer.cpp
sources/shiboken6/ApiExtractor/tests/testcontainer.h
sources/shiboken6/ApiExtractor/tests/testconversionoperator.cpp
sources/shiboken6/ApiExtractor/tests/testconversionoperator.h
sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp
sources/shiboken6/ApiExtractor/tests/testconversionruletag.h
sources/shiboken6/ApiExtractor/tests/testctorinformation.cpp
sources/shiboken6/ApiExtractor/tests/testctorinformation.h
sources/shiboken6/ApiExtractor/tests/testdroptypeentries.cpp
sources/shiboken6/ApiExtractor/tests/testdroptypeentries.h
sources/shiboken6/ApiExtractor/tests/testdtorinformation.cpp
sources/shiboken6/ApiExtractor/tests/testdtorinformation.h
sources/shiboken6/ApiExtractor/tests/testenum.cpp
sources/shiboken6/ApiExtractor/tests/testenum.h
sources/shiboken6/ApiExtractor/tests/testextrainclude.cpp
sources/shiboken6/ApiExtractor/tests/testextrainclude.h
sources/shiboken6/ApiExtractor/tests/testfunctiontag.cpp
sources/shiboken6/ApiExtractor/tests/testfunctiontag.h
sources/shiboken6/ApiExtractor/tests/testimplicitconversions.cpp
sources/shiboken6/ApiExtractor/tests/testimplicitconversions.h
sources/shiboken6/ApiExtractor/tests/testinserttemplate.cpp
sources/shiboken6/ApiExtractor/tests/testinserttemplate.h
sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp
sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.h
sources/shiboken6/ApiExtractor/tests/testmodifyfunction.cpp
sources/shiboken6/ApiExtractor/tests/testmodifyfunction.h
sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.cpp
sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.h
sources/shiboken6/ApiExtractor/tests/testnamespace.cpp
sources/shiboken6/ApiExtractor/tests/testnamespace.h
sources/shiboken6/ApiExtractor/tests/testnestedtypes.cpp
sources/shiboken6/ApiExtractor/tests/testnestedtypes.h
sources/shiboken6/ApiExtractor/tests/testnumericaltypedef.cpp
sources/shiboken6/ApiExtractor/tests/testnumericaltypedef.h
sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.cpp
sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.h
sources/shiboken6/ApiExtractor/tests/testrefcounttag.cpp
sources/shiboken6/ApiExtractor/tests/testrefcounttag.h
sources/shiboken6/ApiExtractor/tests/testreferencetopointer.cpp
sources/shiboken6/ApiExtractor/tests/testreferencetopointer.h
sources/shiboken6/ApiExtractor/tests/testremovefield.cpp
sources/shiboken6/ApiExtractor/tests/testremovefield.h
sources/shiboken6/ApiExtractor/tests/testremoveimplconv.cpp
sources/shiboken6/ApiExtractor/tests/testremoveimplconv.h
sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.cpp
sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.h
sources/shiboken6/ApiExtractor/tests/testresolvetype.cpp
sources/shiboken6/ApiExtractor/tests/testresolvetype.h
sources/shiboken6/ApiExtractor/tests/testreverseoperators.cpp
sources/shiboken6/ApiExtractor/tests/testreverseoperators.h
sources/shiboken6/ApiExtractor/tests/testtemplates.cpp
sources/shiboken6/ApiExtractor/tests/testtemplates.h
sources/shiboken6/ApiExtractor/tests/testtoposort.cpp
sources/shiboken6/ApiExtractor/tests/testtoposort.h
sources/shiboken6/ApiExtractor/tests/testtyperevision.cpp
sources/shiboken6/ApiExtractor/tests/testtyperevision.h
sources/shiboken6/ApiExtractor/tests/testutil.h
sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.cpp
sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.h
sources/shiboken6/ApiExtractor/tests/testvoidarg.cpp
sources/shiboken6/ApiExtractor/tests/testvoidarg.h
sources/shiboken6/ApiExtractor/textstream.h
sources/shiboken6/ApiExtractor/typedatabase.cpp
sources/shiboken6/ApiExtractor/typedatabase.h
sources/shiboken6/ApiExtractor/typedatabase_p.h
sources/shiboken6/ApiExtractor/typedatabase_typedefs.h
sources/shiboken6/ApiExtractor/typedefentry.h
sources/shiboken6/ApiExtractor/typeparser.cpp
sources/shiboken6/ApiExtractor/typeparser.h
sources/shiboken6/ApiExtractor/typesystem.cpp
sources/shiboken6/ApiExtractor/typesystem.h
sources/shiboken6/ApiExtractor/typesystem_enums.h
sources/shiboken6/ApiExtractor/typesystem_typedefs.h
sources/shiboken6/ApiExtractor/typesystemparser.cpp
sources/shiboken6/ApiExtractor/typesystemparser_p.h
sources/shiboken6/ApiExtractor/voidtypeentry.h
sources/shiboken6/ApiExtractor/xmlutils.h
sources/shiboken6/ApiExtractor/xmlutils_libxslt.cpp
sources/shiboken6/ApiExtractor/xmlutils_libxslt.h
sources/shiboken6/ApiExtractor/xmlutils_qt.h
sources/shiboken6/cmake/ShibokenHelpers.cmake
sources/shiboken6/doc/index.rst
sources/shiboken6/doc/scripts/patch_qhp.py
sources/shiboken6/doc/typesystem.rst
sources/shiboken6/doc/typesystem_arguments.rst
sources/shiboken6/doc/typesystem_codeinjection.rst
sources/shiboken6/doc/typesystem_converters.rst
sources/shiboken6/doc/typesystem_documentation.rst
sources/shiboken6/doc/typesystem_manipulating_objects.rst
sources/shiboken6/doc/typesystem_objectvalue.rst [new file with mode: 0644]
sources/shiboken6/doc/typesystem_overloads.rst [new file with mode: 0644]
sources/shiboken6/doc/typesystem_specialfunctions.rst
sources/shiboken6/doc/typesystem_specifying_types.rst
sources/shiboken6/doc/typesystem_templates.rst
sources/shiboken6/doc/typesystem_variables.rst
sources/shiboken6/generator/CMakeLists.txt
sources/shiboken6/generator/defaultvalue.cpp
sources/shiboken6/generator/defaultvalue.h
sources/shiboken6/generator/generator.cpp
sources/shiboken6/generator/generator.h
sources/shiboken6/generator/generatorcontext.cpp
sources/shiboken6/generator/generatorcontext.h
sources/shiboken6/generator/main.cpp
sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp
sources/shiboken6/generator/qtdoc/qtdocgenerator.h
sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp
sources/shiboken6/generator/qtdoc/qtxmltosphinx.h
sources/shiboken6/generator/qtdoc/qtxmltosphinxinterface.h
sources/shiboken6/generator/qtdoc/rstformat.h
sources/shiboken6/generator/shiboken/cppgenerator.cpp
sources/shiboken6/generator/shiboken/cppgenerator.h
sources/shiboken6/generator/shiboken/cppgenerator_container.cpp
sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp
sources/shiboken6/generator/shiboken/ctypenames.h
sources/shiboken6/generator/shiboken/generatorargument.cpp
sources/shiboken6/generator/shiboken/generatorargument.h
sources/shiboken6/generator/shiboken/generatorstrings.h
sources/shiboken6/generator/shiboken/headergenerator.cpp
sources/shiboken6/generator/shiboken/headergenerator.h
sources/shiboken6/generator/shiboken/overloaddata.cpp
sources/shiboken6/generator/shiboken/overloaddata.h
sources/shiboken6/generator/shiboken/overridecacheentry.cpp [new file with mode: 0644]
sources/shiboken6/generator/shiboken/overridecacheentry.h [new file with mode: 0644]
sources/shiboken6/generator/shiboken/pytypenames.h
sources/shiboken6/generator/shiboken/shibokengenerator.cpp
sources/shiboken6/generator/shiboken/shibokengenerator.h
sources/shiboken6/libshiboken/CMakeLists.txt
sources/shiboken6/libshiboken/autodecref.h
sources/shiboken6/libshiboken/basewrapper.cpp
sources/shiboken6/libshiboken/basewrapper.h
sources/shiboken6/libshiboken/bindingmanager.cpp
sources/shiboken6/libshiboken/embed/module_collector.py
sources/shiboken6/libshiboken/embed/signature_bootstrap.py
sources/shiboken6/libshiboken/gilstate.cpp
sources/shiboken6/libshiboken/gilstate.h
sources/shiboken6/libshiboken/helper.cpp
sources/shiboken6/libshiboken/helper.h
sources/shiboken6/libshiboken/pep384impl.cpp
sources/shiboken6/libshiboken/pep384impl.h
sources/shiboken6/libshiboken/sbkarrayconverter.cpp
sources/shiboken6/libshiboken/sbkbindingutils.cpp [new file with mode: 0644]
sources/shiboken6/libshiboken/sbkbindingutils.h [new file with mode: 0644]
sources/shiboken6/libshiboken/sbkconverter.cpp
sources/shiboken6/libshiboken/sbkconverter.h
sources/shiboken6/libshiboken/sbkenum.cpp
sources/shiboken6/libshiboken/sbkenum.h
sources/shiboken6/libshiboken/sbkerrors.cpp
sources/shiboken6/libshiboken/sbkerrors.h
sources/shiboken6/libshiboken/sbkfeature_base.cpp
sources/shiboken6/libshiboken/sbkmodule.cpp
sources/shiboken6/libshiboken/sbkstring.cpp
sources/shiboken6/libshiboken/shiboken.h
sources/shiboken6/libshiboken/signature/signature.cpp
sources/shiboken6/libshiboken/signature/signature_helper.cpp
sources/shiboken6/shiboken_tool.py
sources/shiboken6/shibokenmodule/_config.py.in
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py
sources/shiboken6/tests/dumpcodemodel/main.cpp
sources/shiboken6/tests/libsample/CMakeLists.txt
sources/shiboken6/tests/libsample/derivedusingct.cpp
sources/shiboken6/tests/libsample/derivedusingct.h
sources/shiboken6/tests/libsample/moveonly.h [new file with mode: 0644]
sources/shiboken6/tests/libsample/samplenamespace.cpp
sources/shiboken6/tests/otherbinding/typesystem_other.xml
sources/shiboken6/tests/qtxmltosphinx/CMakeLists.txt
sources/shiboken6/tests/qtxmltosphinx/main.cpp
sources/shiboken6/tests/qtxmltosphinxtest/CMakeLists.txt
sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.cpp
sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.h
sources/shiboken6/tests/samplebinding/CMakeLists.txt
sources/shiboken6/tests/samplebinding/derived_test.py
sources/shiboken6/tests/samplebinding/global.h
sources/shiboken6/tests/samplebinding/objecttype_test.py
sources/shiboken6/tests/samplebinding/objecttypelayout_test.py
sources/shiboken6/tests/samplebinding/ownership_reparenting_test.py
sources/shiboken6/tests/samplebinding/pointerprimitivetype_test.py
sources/shiboken6/tests/samplebinding/protected_test.py
sources/shiboken6/tests/samplebinding/sample_test.py
sources/shiboken6/tests/samplebinding/samplesnippets.cpp
sources/shiboken6/tests/samplebinding/typesystem_sample.xml
sources/shiboken6/tests/smartbinding/typesystem_smart.xml
sources/shiboken6/tests/test_generator/dummygentest.cpp
testing/command.py
testing/parser.py
testing/runner.py
testing/wheel_tester.py
tools/cross_compile_android/android_utilities.py
tools/cross_compile_android/main.py
tools/cross_compile_android/templates/toolchain_default.tmpl.cmake
tools/doc_modules.py
tools/example_gallery/main.py
tools/snippets_translate/converter.py
tools/snippets_translate/enum_migration.py [new file with mode: 0644]
tools/snippets_translate/snippets_translate.pyproject
tools/snippets_translate/tests/test_converter.py
wheel_artifacts/pyproject.toml.base

diff --git a/.QT-ENTERPRISE-LICENSE-AGREEMENT b/.QT-ENTERPRISE-LICENSE-AGREEMENT
deleted file mode 100644 (file)
index 2d225ec..0000000
+++ /dev/null
@@ -1,262 +0,0 @@
-Qt Frame Agreement
-Version 2024-02
-
-1. PARTIES OF THIS AGREEMENT
-1.1. This Qt Frame Agreement—comprised of these general terms together with the appendices attached hereto, (hereinafter “Agreement”) is made by and between: The Qt Company, as defined below (hereinafter ”The Qt Company”) AND Customer name (hereinafter “Customer"):___________________ Business Id (e.g. VAT or EIN number):___________________
-1.2. The parties above are hereinafter individually referred to as a "Party" and collectively as the "Parties".
-
-2. STRUCTURE AND OBJECT OF THE AGREEMENT
-2.1. The Parties have entered into this Agreement to agree on the terms and conditions applicable to The Qt Company's delivery of products and services ("Services") to Customer.
-2.2.  This Agreement is comprised of the following components:
-(i) This Agreement, which contains the general terms applicable to all Services,
-(ii) Appendices for each of the Services, containing terms applicable to that individual set of Services ("Service Terms"),
-(iii) a Qt Appendix for Pricing, if applicable, which contains pricing for specific Services, and
-(iv) other topic-specific appendices, such as Support or Marketing Rights.
-2.3. Any and all Services purchased shall be specified in, and agreed upon between, the Parties under a separate purchase order, statement of work, quote, or similar document ("Purchase Document"). Each Purchase Document concluded under this Agreement shall include a reference to this Agreement and be governed by this Agreement.
-
-3. DEFINITIONS
-3.1. "Affiliate" of a Party shall mean an entity (i) which is directly or indirectly controlling such Party; (ii) which is under the same direct or indirect ownership or control as such Party; or (iii) which is directly or indirectly owned or controlled by such Party. For these purposes, an entity shall be treated as being controlled by another if that other entity has fifty percent (50 %) or more of the votes in such entity, is able to direct its affairs and/or to control the composition of its board of directors or equivalent body.
-3.2. "Contractor" shall mean third-party consultants, distributors and contractors performing services to Customer under an applicable contractual arrangement.
-3.3. "Customer" shall mean the individual or legal entity specified in Section 1 above, that is a Party to this Agreement.
-3.4. "Force Majeure Event" shall have the meaning set forth in Section 11.7.
-3.5. "Licensed Software" shall mean The Qt Company's commercial software product which is licensed for use by Customer under this Agreement and corresponding Service Terms. Licensed Software shall include, if and to the extent applicable and specified in the applicable relevant Service Terms, corresponding online or electronic documentation, associated media and printed materials, including the source code, and example programs. The Qt Company may in the course of its development activities, at its free and absolute discretion and without any obligation to send or publish any notifications to  Customer or in general, make changes, additions or deletions in the components and functionalities of the Licensed Software, provided that no such changes, additions or deletions will affect the already released version of the Licensed Software, but only upcoming version(s). Licensed Software is commercial computer software, developed at private expense and offered to the public under standard commercial terms.
-3.6. "Professional Services" shall mean The Qt Company's professional-, consulting-, training- and/or project services delivered to Customer under this Agreement and specified in a Purchase Document.
-3.7. "Support" shall mean maintenance and support services provided by The Qt Company to assist Customer in using the Licensed Software, as further specified in the Appendix for Support Terms.
-3.8. "The Qt Company" shall mean:
-(i) in the event Customer is an entity residing in the United States or a legal entity incorporated in or having its headquarters in the United States, The Qt Company Inc., a Delaware corporation with its office at 3031 Tisch Way, 110 Plaza West, San Jose, CA 95128, USA.; or
-(ii) in the event Customer is an entity residing outside of the United States or a legal entity incorporated or having its registered office outside of the United States, The Qt Company Oy., a Finnish company with its registered office at Miestentie 7, 02150 Espoo, Finland.
-
-4. PRICES AND PAYMENT
-4.1. The Qt Company agrees to make Services available to Customer subject to the prices set forth in the Appendix for Pricing. In the event that the Appendix for Pricing does not include a price for certain Services, the applicable price shall be the price agreed by the Parties in the respective Purchase Document.
-4.2. All prices are exclusive of value added tax or other taxes, levels, or duties. Value added tax as well as other possible public charges imposed by authorities shall be added to the prices.
-4.3. All fees under this Agreement are non-cancellable and non-refundable.
-4.4. All fees under this Agreement shall be paid by Customer no later than thirty (30) days from the date of the applicable invoice from The Qt Company.
-4.5. Unless otherwise agreed or provided in the respective Service Terms or Purchase Document, The Qt Company will invoice fees for:
-4.5.1. Licensed Software and Support in advance upon conclusion of the Purchase Document, and
-4.5.2. Professional Services monthly in arrears after the Service has been performed.
-4.6. A late payment charge of the lower of: (a) one percent (1%) per month; or (b) the highest interest rate stipulated by applicable law, shall be charged on any unpaid balances that remain past due and which have not been disputed by Customer in good faith within thirty (30) days of receipt of invoice from The Qt Company.
-4.7. The Qt Company may either (i) invoice Customer based on existing agreement, (ii) request Customer to place a purchase order corresponding to a quote by The Qt Company, or (iii) use Customer's stored Credit Card information to automatically charge the Customer for the relevant Renewal Term.
-4.8. Unless and to the extent otherwise agreed in the Appendix for Pricing or in the Purchase Document, The Qt Company shall be entitled to adjust the prices set forth in the Appendix for Pricing by notifying  Customer of the change in writing at least sixty (60) days before the effective date of the change. The change shall not affect the current pricing term of Services agreed upon before the effective date of the change.
-
-5. CONFIDENTIALITY
-5.1. The Parties shall keep confidential, and shall not use or disclose to any unauthorized third parties, the existence and content of this Agreement as well as any Confidential Information received from the other Party or otherwise learned in connection with the Agreement or the performance of the Services, without the prior written consent of the other Party. Confidential Information shall mean information that is designated as confidential or that would be reasonably understood to be confidential given the circumstances of disclosure and the nature of the information. The Parties shall not use Confidential Information received from the other Party for any other purposes than the performance of the Agreement or the fulfilment of their rights and obligations hereunder.
-5.2. Each Party shall limit access the other Party's Confidential Information only to those of its employees, subcontractors, Contractors, Affiliates or financial or legal advisors who necessarily need access to the Confidential Information for the proper performance of the Party's rights and obligations under the Agreement. Each Party shall ensure that the persons receiving Confidential Information of the other Party are bound by confidentiality obligations not less restrictive than those stipulated herein.
-5.3. Each Party shall protect the confidentiality of the other Party's Confidential Information with at least  the same degree of security as it exercises to its own confidential information, but no less than a standard of reasonable care.
-5.4. The confidentiality obligation stipulated herein shall not be applied to material and information which:
-(iii) has become generally available or otherwise public prior to its submission by the other Party;
-(iv) becomes generally available or otherwise public due to a reason other than the negligence or omission of the recipient or its personnel or other actions in violation of this Agreement or  applicable legislation;
-(v) the Party has lawfully received from a third party without any obligation of confidentiality;
-(vi) was lawfully in the possession of the receiving Party prior to receipt of the same from the other Party without any obligation of confidentiality related thereto;
-(vii) a Party has developed independently without using material or information received from the other Party; or
-(viii) a Party must disclose pursuant to law, decree or other order issued by competent regulatory or governmental body or other public authority or a judicial order, in which case the Party shall, to the extent permitted by applicable law, inform the other Party in writing of the disclosure of information prior to such disclosure.
-5.5. Each Party shall, upon request of the other Party at any time, including upon termination, cancellation or expiry of the Agreement, promptly destroy or deliver to the other Party any and all the documents, files, copies and material containing Confidential Information of the other Party. Notwithstanding the foregoing, a Party may retain one copy of the Confidential Information in a secure location, if and solely to the extent required to comply with applicable laws or regulations. Any Confidential Information stored in electronic back-up form shall be rendered inaccessible and destroyed in accordance with standard back-up procedures.
-
-6. INTELLECTUAL PROPERTY RIGHTS
-6.1. Unless and to the extent expressly provided in the respective Service Terms, this Agreement carries no assignment or license to the intellectual property rights of either Party and all such rights are and shall remain the exclusive property of the Party to whom such rights are vested under applicable law at the signing of this Agreement or thereafter.
-6.2. Where The Qt Company's delivery includes any materials owned by a third party, such third party materials shall be governed in all respects by the applicable license terms of such third-party right holders. The Qt Company shall duly inform the Customer whenever such third party materials are included in the Services and of applicable license terms to be followed by the Customer in using such third party materials.
-
-7. FEES AND ORDERING
-7.1. Services Fees. Services Fees are described in the Purchase Document.
-7.2. Ordering Services.
-(i) Customer may purchase Services pursuant to agreed pricing terms or, if no specific pricing terms have been agreed upon, at The Qt Company's standard pricing terms applicable at the time of purchase.
-(ii) Unless expressly otherwise agreed, any price or other term quoted to Customer shall only be valid for the thirty (30) days from the date such price has been quoted.
-
-8. LIMITED WARRANTY AND WARRANTY DISCLAIMER
-8.1. The Qt Company hereby represents and warrants that: (i) it has the power and authority to grant the rights and licenses granted to Customer under this Agreement; (ii) the Licensed Software will operate materially in accordance with its specifications (as set forth in the applicable product documentation or, where relevant, program description); (iii) Professional Services and Support will be performed in a professional, workmanlike manner pursuant to the Agreement; and (iv) during the ten years prior to the effective date of this Agreement, there have not been any claims alleging that the Licensed Software has infringed any intellectual property rights of a third party and, to the knowledge of The Qt Company as of the effective date of this Agreement, no such infringement exists. These warranties do not apply to issues arising from, or relating to, any third-party materials or Customer's use of the Licensed Software in violation of applicable law or the terms of this Agreement.
-8.2. Except to the extent set forth above, the Services are delivered to Customer "as is" and to the maximum extent permitted by applicable law, exclusive of other warranties, whether express, implied, or otherwise.  Customer's sole and exclusive remedy and The Qt Company's entire liability for deficiencies or errors in the Services shall be limited, at The Qt Company's option, to correction of the error, replacement of the Services, re-performance of the Service  or return of the applicable fees paid for the defective Service for the time period during which Customer was not able to utilize the Service as agreed.
-
-9. LIMITATION OF LIABILITY
-9.1. EXCEPT FOR (I) CASES OF GROSS NEGLIGENCE OR INTENTIONAL MISCONDUCT, (II) A BREACH OR VIOLATION OF THE OTHER PARTY'S INTELLECTUAL PROPERTY RIGHTS, OR (III) WHERE REQUIRED BY APPLICABLE LAW, IN NO EVENT SHALL EITHER PARTY BE LIABLE TO THE OTHER PARTY FOR ANY LOST PROFITS, LOSS OF DATA, LOSS OF BUSINESS OR GOODWILL OR ANY OTHER INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE COST, DAMAGES OR EXPENSE OF ANY KIND, HOWSOEVER ARISING UNDER OR IN CONNECTION WITH THIS AGREEMENT.
-9.2. EXCEPT FOR (I) CASES OF GROSS NEGLIGENCE OR INTENTIONAL MISCONDUCT, (II) A BREACH OR VIOLATION OF THE OTHER PARTY'S INTELLECTUAL PROPERTY RIGHTS, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL EITHER PARTY'S TOTAL AGGREGATE LIABILITY UNDER THIS AGREEMENT EXCEED THE AGGREGATE FEES PAID OR PAYABLE TO THE QT COMPANY BY CUSTOMER FOR THE RESPECTIVE LICENSED SOFTWARE OR SERVICE GIVING RISE TO THE LIABILITY. THE FOREGOING LIMITATION WILL NOT APPLY TO CUSTOMER'S OBLIGATION TO PAY THE APPLICABLE FEES CORRESPONDING TO ITS  ACTUAL USE OF LICENSED SOFTWARE OR SERVICES.
-
-10. TERM AND TERMINATION
-10.1. This Agreement shall enter into force upon signing by both Parties and is effective as of the last date of signature.
-10.2. This Agreement shall remain in force until further notice and may be terminated without cause by either Party by no less than three (3) months' prior written notice to the other Party.
-10.3. Termination of a particular Purchase Document and the Services governed thereunder shall be stipulated under the applicable Service Terms.
-10.4. Either Party may terminate this Agreement with immediate effect, if the other Party:
-(i) commits a material breach of the terms of this Agreement (including applicable Service Terms) and has not remedied such breach within a reasonable period of time (which shall be no less than thirty (30) days) of the non-breaching  Party's written notice specifying the breach, or
-(ii) becomes bankrupt, insolvent or goes into liquidation or debt restructuring.
-10.5. Termination of this Agreement shall, as such, have no effect on the validity of any Services ordered and agreed prior to the effective date of such termination, and such Services shall continue to remain in force pursuant to applicable Service Terms (including the terms of this Agreement) for the remainder of the duration of the applicable Service validity term.
-
-11. GOVERNING LAW AND DISPUTE RESOLUTION
-11.1. The United Nations Convention on Contracts for the International Sale of Goods will not apply to this Agreement.
-11.2. Where this Agreement is concluded with The Qt Company, Inc., a Delaware corporation, the Parties agree that this Section 10.2 will apply. This Agreement will be governed by, and construed in accordance with the laws of the State of California and any controlling United States federal law. Any dispute, controversy or claim arising out of or relating to this contract, including the formation, interpretation, breach or termination thereof, and whether the claims asserted are arbitrable, will be referred to and finally determined by arbitration in accordance with the JAMS International Arbitration Rules. The tribunal will consist of one arbitrator. The place of arbitration will be San Francisco, California, USA. The language to be used in the arbitral proceedings will be English. Judgment upon the award rendered by the arbitrator(s) may be entered in any court having jurisdiction thereof. This Section 10.2 shall not preclude parties from seeking provisional remedies in aid of arbitration from a court of appropriate jurisdiction. Notwithstanding the foregoing, any action by The Qt Company solely to collect license or other fees hereunder may be brought in any court of competent jurisdiction.
-11.3. Where this Agreement is concluded with The Qt Company, Oy., a Finnish company, the parties agree that this Section 10.3 will apply. This Agreement shall be construed and interpreted in accordance with the laws of Finland, excluding its choice of law provisions. All disputes arising out of or in connection with this Agreement shall be finally settled in accordance with the laws of Finland, excluding its choice of law provisions.  All disputes arising out of or in connection with this Agreement shall be finally settled under the Rules of Arbitration of the International Chamber of Commerce by one or more arbitrators appointed in accordance with the said Rules. The place of arbitration will be Helsinki, Finland. The language to be used in arbitral proceedings will be English. This Section 10.3 shall not preclude parties from seeking provisional remedies in aid of arbitration from a court of appropriate jurisdiction.
-
-12. MISCELLANOUS
-12.1. No Assignment. Customer shall not be entitled to assign or transfer all or any of its rights, benefits and obligations under this Agreement except in case of sale of relevant business or assets or otherwise with prior written consent of The Qt Company, which shall not be unreasonably withheld or delayed. The Qt Company shall be entitled to freely assign or transfer any of its rights, benefits or obligations under this Agreement.
-12.2. Surviving Sections. Any terms and conditions that by their nature or otherwise reasonably should survive termination of this Agreement shall so be deemed to survive.
-12.3. Entire Agreement. This Agreement, its Appendices and any applicable Purchase Documents constitute the complete agreement between the Parties and supersedes all prior or contemporaneous discussions, representations, contracts (including prior License Agreements and similar prior agreements), and proposals, written or oral, with respect to the subject matters discussed herein.
-12.4. Subcontractors. The Qt Company may utilize subcontractors in the performance of Services under this Agreement, provided that The Qt Company remains responsible for the performance of the Services and compliance with this Agreement, as well as ensuring that subcontractors are required to abide by relevant restrictions (e.g., confidentiality) set forth in this Agreement.
-12.5. Modifications. No modification of this Agreement shall be effective unless contained in a writing executed by an authorized representative of each Party. No standard terms and conditions or provisions of any Customer purchase order or other ordering form that Customer may use in connection with the acquisition of Services will modify or affect this Agreement, the parties agree that any such terms and conditions are void with no legal effect.
-12.6. Affiliate Orders. Customer Affiliates may purchase Services via this Agreement as follows:
-(i) any purchases by Customer Affiliates from The Qt Company or its Affiliates will create a contractual relationship directly between the relevant The Qt Company entity and the respective ordering Customer Affiliate;
-(ii) the entry into a Purchase Document between The Qt Company and Customer Affiliate creates an agreement between The Qt Company and Customer Affiliate and incorporates all terms and conditions of this Agreement as the governing agreement between The Qt Company and Customer Affiliate ("Accession Agreement"): and
-(iii) Customer Affiliate will be deemed "Customer" under the terms of this Agreement and all rights and obligations under such Accession Agreement are vested and borne solely by the ordering Customer Affiliate and the relevant The Qt Company entity as contracting parties under such Accession Agreement.
-12.7. Force Majeure. Neither Party shall be liable to the other for any delay or non-performance of its obligations hereunder in the event and to the extent that such delay or non-performance is due to an event of act of God, terrorist attack or other similar unforeseeable catastrophic event that prevents either Party for fulfilling its obligations under this Agreement and which such Party cannot avoid or circumvent ("Force Majeure Event"). If the Force Majeure Event results in a delay or non-performance of a Party for a period of three (3) months or longer, then either Party shall have the right to terminate the relevant Purchase Document and Services thereunder with immediate effect without any liability (except for the obligations of payment arising prior to the Force Majeure Event) towards the other Party.
-12.8. Notices. Any notice given by one Party to the other shall be deemed properly given and deemed received if specifically acknowledged by the receiving Party in writing or when successfully delivered to the recipient by hand, fax, or special courier during normal business hours on a business day to the addresses specified for each Party in this Agreement. Each communication and document made or delivered by one Party to the other Party pursuant to this Agreement shall be in the English language.
-12.9. Attorney Fees. The prevailing Party in any action to enforce this Agreement shall be entitled to recover its attorney's fees and costs in connection with such action.
-12.10. Privacy and Security. The Parties commit to and comply with their respective applicable obligations under the privacy and security terms set forth in the Privacy and Security Appendix and relevant Appendices attached hereto.
-12.11. Feedback. Customer agrees that, from time to time, The Qt Company, may request feedback from Customer regarding the Services ("Feedback"). Customer may choose to provide Feedback and agrees that The Qt Company may freely use, copy, disclose, and exploit any Feedback. No Feedback will be considered Customer Confidential Information unless explicitly agreed otherwise between the Parties.
-12.12. Export Control. Customer acknowledges that the Services, or portions thereof, may be subject to export control restrictions under the applicable laws of respective countries. Customer shall fully comply with all applicable export license restrictions and requirements, economic sanctions restrictions, as well as with all laws and regulations relating thereto, and shall procure all necessary governmental authorizations, including without limitation, all necessary licenses, approvals, permissions, or consents, where necessary (e.g., for re-exportation of the Redistributables, Applications and/or Devices, each as defined in the relevant Service Terms).
-12.13. Severability. If any provision of this Agreement shall be adjudged by any court of competent jurisdiction to be unenforceable or invalid, that provision shall be limited or eliminated to the minimum extent necessary so that this Agreement shall otherwise remain in full force and effect and enforceable.
-
-13. APPENDICES
-13.1. The following appendices form an integral part of this Agreement. In case of a discrepancy between this Agreement and any of its Appendices, this Agreement shall prevail. In case of discrepancies between the Purchase Document(s) and this Agreement or applicable Service Terms, the terms of this Agreement or the applicable Service Terms shall prevail, except in cases where an express deliberate deviation from the terms of this Agreement or applicable Service Terms has been concluded pursuant to Section 2.3 hereof, in which case the Purchase Document shall prevail.
-1. Appendix for Qt Development Framework
-2. Appendix for Support Terms https://www.qt.io/terms-conditions/support-terms
-3. Appendix for Privacy and Security Terms https://www.qt.io/terms-conditions/privacy-and-security
-
-Appendix for Qt Development Framework
-Version 2024-02
-
-1. This Appendix for Qt Development Framework is an integral part of the Agreement and specifies the legal terms for the licensing of Licensed Software (as defined below) between The Qt Company and the Customer. Entry into this Appendix governs the use of and supersedes any prior contracts between the Parties (including prior License Agreements and similar prior agreements), with respect to the Licensed Software under this Appendix.
-
-2. DEFINITIONS
-2.1. Capitalized words used in this Appendix shall have the meanings described in the Agreement or as defined below.
-2.2. "Add-on Products" shall mean The Qt Company's specific add-on software products which are not licensed as part of The Qt Company's standard Services offerings, but shall be included into the scope of Licensed Software only if so specifically agreed between the Parties.
-2.3. "Application" means software products created using the Licensed Software, which include the Redistributables, or part thereof.
-2.4. "End Customer" shall mean Customer's customer(s) to whom Customer, directly or indirectly, distributes copies of the Redistributables as integrated or incorporated into Applications or Devices.
-2.5. "Data Protection Legislation" shall mean the General Data Protection Regulation (EU 2016/679) (GDPR) and any national implementing laws, regulations and secondary legislation, as may be amended or updated from time to time, as well as any other data protection laws or regulations applicable in the relevant territory.
-2.6. "Deployment Platforms" shall mean target operating systems and/or hardware specified in the License Certificate, on which the Redistributables can be distributed pursuant to the terms and conditions of this Appendix.
-2.7. "Designated User(s)" shall mean the employee(s) of Customer or Customer's Affiliates acting within the scope of their employment or Customer's Contractors acting within the scope of their services on behalf of Customer.
-2.8. "Development License" shall mean the license needed by the Customer for each Designated User to use Licensed Software under the license grant described in Section 5 of this Appendix. Development Licenses are available per respective Licensed Software products; each product having its designated scope and purpose of use.
-2.9. "Development Platforms" shall mean the host operating system(s) specified in the License Certificate, on which Licensed Software can be used under the Development License.
-2.10. "Devices" shall mean
-(i) hardware devices or products that
-a. are manufactured and/or distributed by the Customer, its Affiliates, Contractors or End Customer, and
-b. incorporate, integrate or link to Applications such that substantial functionality of such unit, when used by an End User, is provided by Application(s) or otherwise depends on the Licensed Software; or
-(ii) Applications designed for the hardware devices specified in item (i).
-Devices covered by this Appendix shall be specified in the Pricing Appendix or Purchase Document.
-2.11. "Distribution License(s)" shall mean a royalty-bearing license required for any kind of sale, trade, exchange, loan, lease, rental or other distribution by or on behalf of Customer to a third party of Redistributables in connection with Devices pursuant to license grant described in Section 5.3 of this Appendix. Distribution Licenses are sold separately for each type of Device respectively and cannot be used for any other type of Devices.
-2.12. "Distribution License Packs" shall mean set of prepaid Distribution Licenses for distribution of Redistributables, as defined in The Qt Company's standard price list, quote, Pricing Appendix  or in the Purchase Document, as applicable.
-2.13. "Evaluation License Term" shall mean a time period specified in the License Certificate for the Customer to use the relevant Licensed Software for evaluation purposes according to Section 5.6 of this Appendix.
-2.14. "Intellectual Property Rights" shall mean patents (including utility models), design patents, and designs (whether or not capable of registration), chip topography rights and other like protection, copyrights, trademarks, service marks, trade names, logos or other words or symbols and any other form of statutory protection of any kind and applications for any of the foregoing as well as any trade secrets.
-2.15. "License Certificate" shall mean a certificate generated by The Qt Company for each Designated User respectively upon their download of the Licensed Software, which will be available under the respective Designated User's Qt Account at account.qt.io. License Certificates will specify relevant information pertaining to the Licensed Software purchased by Customer and the license to the Licensed Software.
-2.16. "License Fee" shall mean the fee charged to Customer for rights granted under this Appendix.
-2.17. "Licensed Software" shall mean the specified product(s) of Qt Software which Customer has purchased and which is provided to Customer under the terms of this Appendix (including its Exhibits). Licensed Software shall include corresponding online or electronic documentation, associated media and printed materials, including source code (where applicable), example programs and the documentation. Licensed Software does not include Third Party Software (as defined in Section 6) or Qt Community Edition. The Qt Company may, in the course of its development activities, at its free and absolute discretion and without any obligation to send or publish any notifications to Customer or in general, make changes, additions or deletions in the components and functionalities of the Licensed Software, provided that no such changes, additions or deletions will affect the already released version of the Licensed Software, but only upcoming version(s).
-2.18. "License Term" shall mean the agreed validity period of the Development License during which the relevant Licensed Software product can be used pursuant to this Appendix. The agreed License Term, as ordered and paid for by Customer, shall be memorialized in the applicable License Certificate.
-2.19. "Customer's Records" shall mean books and records that contain information bearing on Customer's compliance with the  Agreement, Customer's use of Qt Community Edition and/or the payments due to The Qt Company under the Agreement, including, but not limited to user information, assembly logs, sales records and distribution records.
-2.20. "Modified Software" shall have the meaning as set forth below in Section 4.
-2.21. "Qt Software" shall mean the development and design software of The Qt Company, which The Qt Company makes available under commercial and/or open source licenses as either the "Licensed Software" or the "Qt Community Edition".
-2.22. "Permitted Software" shall mean third party products that are generally available to the public, which may include parts of Qt Community Edition or be developed using Qt Community Edition.
-2.23. "Pre-Release Code" shall have the meaning as set forth in Section 7.
-2.24. "Prohibited Combination" shall mean any effort to use, combine, incorporate, link or integrate Licensed Software with any software created with or incorporating Qt Community Edition, or use Licensed Software for creation of any such software.
-2.25. "Qt Community Edition" shall mean  the open source version of Qt Software available under the terms of the GNU Lesser General Public License, version 2.1 or later ("LGPL") or the GNU General Public License, version 2.0 or later ("GPL"). For clarity, Qt Community Edition shall not be provided, governed or used under this Appendix.
-2.26. "Redistributables" shall mean the portions of Licensed Software as set forth in Exhibit 1 hereto that may be distributed pursuant to this Appendix in object code form only, including any relevant documentation. Where relevant, any reference to Licensed Software in this Appendix includes and refers  to Redistributables.
-2.27. "Renewal Term" shall mean an extension of the previous License Term as agreed between the Parties.
-2.28. "Submitted Modified Software" shall have the meaning as set forth in Section 4.2 of this Appendix.
-2.29. "Third-Party Software" shall have the meaning set forth in Section 6 of this Appendix.
-2.30. "Updates" shall mean a release or version of the Licensed Software containing bug fixes, error corrections and other changes that are generally made available to users of the Licensed Software that have contracted for Support. Updates are generally depicted as a change to the digits following the decimal in the Licensed Software version number. The Qt Company shall make Updates available to Customer under the Support. Updates shall be considered as part of the Licensed Software hereunder.
-2.31. "Upgrades" shall mean a release or version of the Licensed Software containing enhancements and new features and are generally depicted as a change to the first digit of the Licensed Software version number. In the event that Upgrades are provided to Customer under this Appendix, they shall be considered as part of the Licensed Software hereunder.
-
-3. OWNERSHIP
-3.1. Ownership of The Qt Company
-3.1.1. The Licensed Software is protected by copyright laws and international copyright treaties, as well as other intellectual property laws and treaties. The Licensed Software is licensed, not sold.
-3.1.2. All of The Qt Company's Intellectual Property Rights are and shall remain the exclusive property of The Qt Company or its respective licensors . No rights to The Qt Company's Intellectual Property Rights are assigned or granted to Customer under this Appendix, except when and to the extent expressly specified herein.
-3.2. Ownership of Customer
-3.2.1. All of Customer's Intellectual Property Rights are and shall remain the exclusive property of Customer or its licensors respectively.
-3.2.2. Except to the extent set forth in this Appendix, all Intellectual Property Rights to the Modified Software, Applications and Devices (except to Redistributables included therein) shall remain with Customer.
-
-4. MODIFIED SOFTWARE
-4.1. Customer may create bug-fixes, error corrections, patches or modifications to the Licensed Software ("Modified Software"). To the extent that Customer's Modified Software breaks source or binary compatibility or other functionality with the Licensed Software, Customer acknowledges that The Qt Company's ability to provide Support may be prevented or limited and Customer's ability to make use of Updates may be restricted.
-4.2. Customer may, at its sole and absolute discretion, choose to submit Modified Software to The Qt Company ("Submitted Modified Software") in connection with Customer's Support request, service request or otherwise. In the event Customer does so, then, Customer hereby grants The Qt Company a sublicensable, assignable, irrevocable, perpetual, worldwide, non-exclusive, royalty-free and fully paid-up license, under all of Customer's Intellectual Property Rights, to reproduce, adapt, translate, modify, and prepare derivative works of, publicly display, publicly perform, sublicense, make available and distribute such Submitted Modified Software as The Qt Company sees fit at its free and absolute discretion.
-
-5. LICENSES GRANTED
-5.1. Development with Licensed Software
-5.1.1. Subject to the terms of the Agreement, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable license, valid for each License Term, to use, modify and copy the Licensed Software by Designated Users on the Development Platforms for the sole purposes of designing, developing, demonstrating and testing Application(s) and/or Devices, and to provide support and other services related to such Applications and Devices to End Customers. Each Application and/or Device can only include, incorporate or integrate contributions by such Designated Users who are duly licensed for the applicable Development Platform(s) and Deployment Platform(s) (i.e have a valid license for the appropriate Licensed Software product and only use one type of Qt Development License per Customer Application and/or Device(s)).
-5.1.2. Customer may install copies of the Licensed Software on five (5) computers per Designated User, provided that only Designated Users who have a valid Development License may use the Licensed Software.
-5.1.3. Customer may designate another Designated User to replace a then-current Designated User by notifying The Qt Company in writing, where such replacement is due to termination of employment, long-term absence or other permanent reason affecting Designated User's need for Licensed Software.
-5.1.4. Upon expiry of the initially agreed License Term, the respective License Term shall be automatically extended by one or more Renewal Term(s), unless and until either Party notifies the other Party in writing, that it does not wish to continue the License Term, such notification to be provided to the other Party no less than thirty (30) days before expiry of the respective License Term. The Qt Company shall, in good time before the due date for the above notification, remind the Customer on the coming Renewal Term. Unless otherwise agreed between the Parties, Renewal Term shall be equal to the length of the previous License Term, but no longer than thirty-six (36) months.
-5.1.5. Any such Renewal Term shall be subject to License Fees agreed between the Parties or, if no advance agreement exists, subject to The Qt Company's standard list pricing applicable at the commencement date of any such Renewal Term.
-5.2. Distribution of Applications
-5.2.1. Subject to the terms of the Agreement, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable, perpetual, royalty-free and revocable (only for Customer’s material breach of agreement) right and license to:
-(i) distribute, by itself or through its Contractors, Redistributables as installed, incorporated or integrated into Applications for execution on the Deployment Platforms; and
-(ii) grant perpetual and irrevocable sublicenses to Redistributables, as distributed hereunder, for End Customers solely to the extent necessary in order for the End Customers to use the Applications for their respective intended purposes.
-5.2.2. Right to distribute the Redistributables as part of an Application as provided herein is not royalty-bearing but is conditional upon the Application having been created, updated and maintained under a valid and duly paid Development License.
-5.3. Distribution of Devices
-5.3.1. Subject to the terms of the Agreement, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable, perpetual, revocable (only for Customer’s material breach of agreement), royalty-bearing right and license to:
-(i) distribute, by itself or through one or more tiers of Contractors, Redistributables as installed, incorporated or integrated, or intended to be installed, incorporated or integrated into Devices for execution on the Deployment Platforms; and
-(ii) grant perpetual and irrevocable sublicenses to Redistributables, as distributed hereunder, for End Customers solely to the extent necessary in order for the End Customers to use the Devices for their respective intended purposes.
-5.3.2. Right to distribute the Devices as provided herein is conditional upon (i) the Devices having been created, updated and maintained under a valid and duly paid Development License, and (ii) Customer having acquired corresponding Distribution Licenses at the time of distribution of any Devices to End Customers.
-5.4. Further Requirements
-5.4.1. The licenses granted in this Section 5 by The Qt Company to Customer are conditional and subject to Customer's compliance with the following terms:
-(i) Customer acknowledges that The Qt Company has separate products for the purpose of Applications and Devices respectively, where development and distribution of Devices is only allowed using the correct designated product. Customer shall ensure and bear the burden of proof that Customer is using a correct product entitling Customer to development and distribution of Devices;
-(ii) Customer shall not remove or alter any copyright, trademark or other proprietary rights notice(s) contained in any portion of the Licensed Software;
-(iii) Applications must add primary and substantial functionality to Licensed Software so as not to compete with the Licensed Software;
-(iv) Applications may not pass on functionality which in any way makes it possible for others to create software with Licensed Software; provided however that Customer may use Licensed Software’s scripting and QML ("Qt Quick") functionality solely in order to enable scripting, themes and styles that augment the functionality and appearance of the Application(s) without adding primary and substantial functionality to the Application(s);
-(v) Customer shall not use Licensed Software in any manner or for any purpose that infringes, misappropriates or otherwise violates any Intellectual Property Right or right of any third party, or that violates any applicable law;
-(vi) Customer shall not use The Qt Company's or any of its suppliers' names, logos, or trademarks to market Applications, except that Customer may use “Built with Qt” logo to indicate that an Application or Device was developed using Licensed Software;
-(vii) Customer shall not distribute, sublicense or disclose source code of Licensed Software to any third party (provided however that Customer may appoint employee(s) of Contractors and Affiliates as Designated Users to use Licensed Software pursuant to this Appendix).
-(viii) Customer shall not grant the End Customers a right to: (a) make copies of the Redistributables except when and to the extent required to use the Applications and/or Devices for their intended purpose; (b) modify the Redistributables or create derivative works thereof; (c) decompile, disassemble or otherwise reverse engineer Redistributables; or (d) redistribute any copy or portion of the Redistributables to any third party, except as part of the onward sale of the Application or Device on which the Redistributables are installed;
-(ix) Customer shall not, and shall cause that its Affiliates or Contractors shall not, use Licensed Software in any Prohibited Combination, unless Customer has received specific advance written permission from The Qt Company to do so. Absent such written permission, any and all distribution by Customer during the term of the Agreement of a hardware device or product: a) which incorporates or integrates any part of Licensed Software or Qt Community Edition; or b) where substantial functionality is provided by software built with Licensed Software or Qt Community Edition or otherwise depends on Licensed Software or Qt Community Edition, shall be considered to be Device distribution under this Appendix and shall be dependent on Customer’s compliance thereof (including but not limited to the obligation to pay applicable License Fees for such distribution). Notwithstanding the foregoing, Customer is entitled to use and combine Licensed Software with Permitted Software;
-(x) Customer shall cause all of its Affiliates, Contractors and End Customer entitled to make use of the licenses granted under this Appendix, to be contractually bound to comply with the relevant terms hereof and not to use the Licensed Software beyond the terms hereof nor for any purposes other than operating within the scope of their services for Customer. Customer shall be responsible for any and all actions and omissions of its Affiliates, Contractors, and End Customers relating to the Licensed Software and use thereof (including but not limited to payment of all applicable License Fees);
-(xi) Except when and to the extent explicitly provided in this Section 5, Customer shall not transfer, publish, disclose, display or otherwise make available the Licensed Software; and
-(xii) Customer shall not attempt or enlist a third party to conduct or attempt to conduct any of the above.
-5.4.2. The above terms shall not be applicable if and solely to the extent they conflict with any mandatory provisions of applicable laws.
-5.4.3. Any use of Licensed Software beyond the provisions of this Appendix is strictly prohibited and requires, at a minimum an additional license from The Qt Company (e.g. certain additional rights granted under software development kit “SDK” agreement with regard to limitations of Section 5.4.1 iv, vii or viii).
-5.5. Evaluation License
-5.5.1. Subject to the terms of this Appendix, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable license, valid for the Evaluation License Term to use the relevant Licensed Software product solely for Customer’s internal use to evaluate and determine whether the Licensed Software meets Customer's business requirements, specifically excluding any commercial use of the Licensed Software or any derived work thereof.
-5.5.2. Upon the expiry of the Evaluation License Term, Customer must either discontinue use of the relevant Licensed Software or acquire a commercial Development License specified herein.
-
-6. THIRD-PARTY SOFTWARE. The Licensed Software may provide links or access to third party libraries or code (collectively "Third-Party Software") to implement various functions. Third-Party Software does not, however, comprise part of the Licensed Software, but is provided to Customer complimentary and use thereof is discretionary for Customer. Third-Party Software will be listed in the ".../src/3rdparty" source tree delivered with the Licensed Software or documented in the Licensed Software, as such may be amended from time to time. Customer acknowledges that use or distribution of Third-Party Software is in all respects subject to applicable license terms of applicable third-party right holders.
-
-7. PRE-RELEASE CODE
-7.1. The Licensed Software may contain pre-release code and functionality, or sample code marked or otherwise stated with appropriate designation such as "Technology Preview", "Alpha", "Beta", "Experimental", "Sample", "Example" etc. ("Pre-Release Code").
-7.2. Such Pre-Release Code may be provided complimentary for  Customer, in order to provide experimental support or information for new platforms or preliminary versions of one or more new functionalities, or for other similar reasons. Pre-Release Code may not be at the level of performance and compatibility of a final, generally available, product offering.  Pre-Release Code may not operate correctly, may contain errors and may be substantially modified by The Qt Company prior to a commercial product release, if any. The Qt Company is under no obligation to make Pre-Release Code commercially available, or provide any Support or Updates relating thereto. To the maximum extent permitted by law, the Qt Company assumes no liability whatsoever regarding any Pre-Release Code and any use thereof is exclusively at Customer's own risk and expense.
-7.3.  Unless Licensed Software specifies different license terms for the respective Pre-Release Code, Customer is entitled to use such pre-release code pursuant to Section 5 of this Appendix, just like other Licensed Software.
-
-8. SUPPORT. Support is provided according to agreed support level and subject to applicable requirements and restrictions, as specified in the Appendix for Support Terms.
-
-9. FEES AND ORDERING: DISTRIBUTION LICENSES
-9.1. Distribution License Packs
-9.1.1. Unless otherwise agreed in writing, Distribution Licenses shall be purchased by way of Distribution License Packs.
-9.1.2. Upon due payment of the ordered Distribution License Pack(s), Customer will have an account of Distribution Licenses available for distributing the Redistributables in accordance with this Agreement.
-9.2. Each time Customer distributes a copy of Redistributables, one Distribution License is used and Customer's account of available Distribution Licenses is decreased accordingly.
-9.3. Customer may distribute copies of the Redistributables so long as Customer has Distribution Licenses remaining on its account.
-
-10. RECORD-KEEPING AND REPORTING OBLIGATIONS; AUDIT RIGHTS
-10.1. Customer's Record-keeping
-10.1.1. Customer shall at all times during the term of the Agreement or validity of any of the licenses hereunder, whichever is later, and for a period of two (2) years thereafter, maintain Customer's Records in an accurate and up-to-date form. Customer's Records shall be adequate to reasonably enable The Qt Company to determine Customer's compliance with the provisions of the Agreement. The records shall conform to general good accounting practices.
-10.1.2. Customer shall, within thirty (30) days from receiving The Qt Company's request to that effect, deliver to The Qt Company a report based on Customer's Records, such report to contain information, in sufficient detail, on: (i) number and identity of users working with Licensed Software or Qt Community Edition, (ii) copies of Redistributables distributed by Customer during the most recent calendar quarter and/or any other term specified by The Qt Company, and (iii) any other information pertaining to Customer's compliance with the terms of the Agreement (e.g. information on products and/or projects relating to use of Distribution Licenses), as The Qt Company may reasonably require from time to time.
-10.2. The Qt Company's Audit Rights
-10.2.1. The Qt Company or an independent auditor acting on behalf of The Qt Company may, upon at least thirty (30) days' prior written notice and at The Qt Company expense, audit Customer with respect to Customer's use of the Licensed Software, but not more frequently than once during each six (6) month period. Such audit may be conducted by mail, electronic means or through an in-person visit to Customer's place of business. Any possible in-person audit shall be conducted during regular business hours at Customer's facilities, shall not unreasonably interfere with Customer's business activities and shall be limited in scope to verify Customer's compliance with the terms of the Agreement. The Qt Company or its independent auditor shall be entitled to inspect Customer's Records and conduct necessary interviews of Customer's relevant employees and Contractors. All Customer's Records and use thereof shall be subject to the obligation of confidentiality under the Agreement.
-10.2.2. If an audit reveals that Customer is using the Licensed Software beyond scope of the licenses Customer has paid for, Customer shall pay to The Qt Company any amounts owed for such unauthorized use within thirty (30) days from receipt of the corresponding invoice from The Qt Company.
-10.2.3. In addition, in the event the audit reveals a material violation of the terms of the Agreement (without limitation, either (i) underpayment of more than 10% of License Fees or 10,000 euros (whichever is more) or (ii) distribution of products, which include or result from Prohibited Combination, shall be deemed a material violation for purposes of this section), then Customer shall pay The Qt Company's reasonable cost of conducting such audit.
-
-11. TERMINATION
-11.1. Termination of Licenses
-11.1.1. The Qt Company may terminate Customer's rights to any and all Licensed Software (including access to Support), if Customer:
-(i) commits a material breach of the Agreement (including this Appendix) and has not remedied the breach within a reasonable period of time (which shall be no less than 30 days) of The Qt Company's written notice specifying the breach, or
-(ii) becomes bankrupt, insolvent or goes into liquidation or debt restructuring.
-11.2. Suspension of rights: Instead of termination, The Qt Company reserves the right to suspend or withhold grants of any and all rights to the Licensed Software (including Support), should Customer fail to make payment in timely fashion or otherwise violate or is reasonably suspected of violating its obligations under the Agreement and/or this Appendix, and where such violation or breach is not cured within ten (10) business days following The Qt Company's written notice thereof.
-11.3. Parties  Rights and Duties upon Termination
-11.3.1. Upon expiry or termination of the Development Licenses, Customer shall cease and shall cause all Designated Users (including those of its Affiliates' and Contractors') to cease using the relevant Licensed Software.
-11.3.2. Upon such expiry or termination of Development Licenses, Customer shall destroy or return to The Qt Company all copies of the respective Licensed Software and all related materials and will certify the same by Customer's duly authorized officer to The Qt Company upon its request, provided however that Customer may retain and utilize such copies of the Licensed Software to the extent required to provide Customer's continued support to End Customers,  for archiving purposes or as is required under applicable law.
-11.3.3. Distribution Licenses are perpetual and, therefore, Customer's distribution rights hereunder shall only terminate upon The Qt Company's termination of Distribution Licenses due to Customer's material breach as set forth in Section 11.1.1(i) of this Appendix. In case of such termination by The Qt Company due to Customer's material breach, Customer must cease any distribution of Applications and Devices at the effective date of termination.
-11.3.4. Expiry or termination of any of Customer's licenses hereunder for any reason whatsoever shall not:
-(i) relieve Customer of its obligation to pay any License Fees accrued or payable to The Qt Company prior to the effective date of termination, and Customer pay to The Qt Company all such fees within 30 days from the effective date of termination of the licenses;
-(ii) relieve Customer of its obligation to ensure that Applications and Devices (including those already distributed) remain in compliance with the terms of the Agreement; nor
-(iii) affect any rights of End Customer to continue use of Applications and Devices (and therein incorporated Redistributables).
-11.4. Extension of Rights under Special Circumstances. In the event that, during the applicable License Term, The Qt Company is declared bankrupt under a final, non-cancellable decision by relevant court of law, and the Agreement is not, at the date of expiry of the Development License(s), assigned to a party who has assumed The Qt Company's position as a legitimate licensor of Licensed Software under the Agreement, then all valid Development Licenses possessed by Customer at such date of expiry, and which Customer has not notified for expiry, shall be extended to be valid in perpetuity under the terms of the Agreement. Any such extension shall not apply to The Qt Company's Support obligations.
-
-EXHIBIT 1, Licensed Software
-At the time of conclusion of this Appendix, the latest available version of Licensed Software includes the software libraries and tools set forth in Exhibit 1 (as provided below), depending on which product(s) Customer has purchased under the relevant Purchase Document.
-The modules and tools are specific to each product version respectively and may vary from version to version. Modules and tools included in the latest publicly available version of the respective product at any given time are listed in Exhibit 1 of  https://www.qt.io/terms-conditions/qt-dev-framework/exhibit-1. If a new version of Licensed Software does not include a module or tool present in an older version which Customer is entitled to use under a valid license from The Qt Company, then Customer will continue to have such right during the validity of Customer's license to relevant Licensed Software.  In the event a new version of the Licensed Software adds modules or tools to any previous version(s), Customer's rights will extend to cover also such additional modules and tools.
-
-EXHIBIT 2 - Small Business Terms
-1. This Exhibit applies to entities that qualify as a Qualified Small Business (defined below) and provides additional terms and conditions applicable to small business pricing and licensing. In the event that Customer is a Qualified Small Business and there is any conflict between the terms of this Exhibit and any other terms of the Agreement, the terms in this Exhibit shall take precedence.
-
-2. APPLICABILITY FOR SMALL BUSINESS LICENSES. Any small business discounts applied require that Customer (including any Customer Affiliates or group entities) has an annual revenue (including annual capital funding) below 1 Million EUR, or the equivalent thereof, as approved by The Qt Company (each, a "Qualified Small Business"). The annual revenue, including funding, must be evidenced upon request by business records and approved by The Qt Company in its reasonable discretion.
-
-3. SUPPORT. Support is limited to: (i) Install Support; and (ii) for any other Standard Support issue, five (5) support tickets annually.
-
-4. LIMITATION ON NUMBER OF SMALL BUSINESS DEVELOPER LICENSES. Qualified Small Business discounts and purchasing structure may be applied to a maximum of  three discounted developer licenses (either ADE or DCP) per Qualified Small Business. Any additional licenses purchased will be at The Qt Company list price in effect at the time.
-
-5. LIMITATION FOR NUMBER OF INSTALLATIONS. Customer may install copies of the Licensed Software on two (2) computers per Designated User, provided that only the Designated Users who have a valid Development License may use the Licensed Software.
-
-6. CONDITIONAL WAIVER OF DISTRIBUTION LICENSES. For Qualified Small Businesses, the Agreement requirements to purchase Distribution Licenses for Devices shall apply only when Customer ceases to be a Qualified Small Business (e.g., when annual revenue threshholds  are bypassed).
-
-7. ADDITIONAL TERMS FOR RENEWALS. The initial subscription purchase term for Qualified Small Business Licenses is twelve (12) months. Upon expiration of the initial twelve (12) month term and unless terminated in accordance with the Agreement, the Licenses will automatically renew for additional twelve (12) month terms with applicable Qualified Small Business discounts. If Customer ceases to be a Qualified Small Business, renewal pricing shall be at The Qt Company list price in effect at the time of renewal, or as agreed in writing between the parties.
-
-8. ADDITIONAL AUDIT RIGHTS. In addition to the audit rights set forth in the Agreement, The Qt Company reserves the right to audit Customer financial records in order to determine whether Customer is a Qualified Small Business.
diff --git a/.gitreview b/.gitreview
new file mode 100644 (file)
index 0000000..e92d8e5
--- /dev/null
@@ -0,0 +1,4 @@
+[gerrit]
+host=codereview.qt-project.org
+project=pyside/pyside-setup
+defaultbranch=dev
index 9178660d5118c9b70b4e2a6002e91ddcec2264d7..95e75a4c6dba896247e3971eeb6a14562ba81bd8 100644 (file)
@@ -88,7 +88,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)!
 
 ### Licensing
 
-PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial
+PySide6 is available under both Open Source (LGPLv3 or GPLv2 or GPLv3) and commercial
 license. Using PyPi is the recommended installation source, because the
 content of the wheels is valid for both cases. For more information, refer to
 the [Qt Licensing page](https://www.qt.io/licensing/).
index a42576706be5c6e0c5bea53247537089adc42f52..e232565a2efdb309b3bc2e34b17ca98f9ce1c614 100644 (file)
@@ -67,7 +67,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)!
 
 ### Licensing
 
-PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial
+PySide6 is available under both Open Source (LGPLv3 or GPLv2 or GPLv3) and commercial
 license. Using PyPi is the recommended installation source, because the
 content of the wheels is valid for both cases. For more information, refer to
 the [Qt Licensing page](https://www.qt.io/licensing/).
index 7f96c19b117b416347b5cfd38e844679ff97ac33..73d8b66c25b2ae9988c2566828f001303b18ed90 100644 (file)
@@ -51,7 +51,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)!
 
 ### Licensing
 
-PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial
+PySide6 is available under both Open Source (LGPLv3 or GPLv2 or GPLv3) and commercial
 license. Using PyPi is the recommended installation source, because the
 content of the wheels is valid for both cases. For more information, refer to
 the [Qt Licensing page](https://www.qt.io/licensing/).
index 15e3181514974ac741a7bcb96b4d85626b56270a..b176139dfde45e057ce398d70845a5508f3f3d88 100644 (file)
@@ -28,7 +28,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)!
 
 ### Licensing
 
-PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial
+PySide6 is available under both Open Source (LGPLv3 or GPLv2 or GPLv3) and commercial
 licenses. Using PyPi is the recommended installation source, because the
 content of the wheels is valid for both cases. For more information, refer to
 the [Qt Licensing page](https://www.qt.io/licensing/).
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644 (file)
index 0000000..2b81356
--- /dev/null
@@ -0,0 +1,12 @@
+# Security Policy
+
+This repository contains the source code for the PySide
+, Shiboken, and Shiboken Generator packages, which are
+under the Qt Project.
+
+The Qt Project has the security policy defined
+in the [QUIP-15](https://contribute.qt-project.org/quips/15)
+
+For reporting a vulnerability, please follow the instructions
+on [QUIP-15](https://contribute.qt-project.org/quips/15)
+before sending an email to `security at qt-project dot org`.
index 5f0183f5bb441c53ce6540e75936844d0d1ad0f9..adfb0c6023decbddbe89980b977e05a7a9abae4d 100644 (file)
     win32 ci
     linux
     darwin
-[QtMultimedia::audio_test]
-    linux
-    darwin
-    win32
 # Cannot create metal surface
 [QtMultimediaWidgets::qmultimediawidgets]
     darwin ci
 # Open GL functions failures on macOS (2/2020)
 [QtQml::qqmlnetwork_test]
     linux ci    # extended, see PyPy section below
-[QtWidgets::bug_750]
-    darwin ci
-[QtWidgets::qpicture_test]
-    darwin ci
 [QtAsyncio::qasyncio_test_chain]
     win32
 [QtQml::bug_825]
-    py3.8       # bug in typeobject::type_mro_modified, fix in 3.9
     py3.9.0
     py3.9.1
     py3.9.2
index f11fbb5e789247290a4d52faa2e13ccb9b9af51a..efafc7688a3b91625ceef5dd0112bdfb09dd9aaf 100644 (file)
@@ -78,9 +78,9 @@ class Config(metaclass=Singleton):
         self.setup_kwargs['long_description_content_type'] = 'text/markdown'
 
         self.setup_kwargs['keywords'] = _pyproject_data["keywords"]
-        _author, _email = _pyproject_data["authors"][0]
-        self.setup_kwargs['author'] = _author
-        self.setup_kwargs['author_email'] = _email
+        _author = _pyproject_data["authors"][0]
+        self.setup_kwargs['author'] = _author["name"]
+        self.setup_kwargs['author_email'] = _author["email"]
         self.setup_kwargs['url'] = _pyproject_data["urls"]["Homepage"]
         self.setup_kwargs['license'] = _pyproject_data["license"]["text"]
         self.setup_kwargs['python_requires'] = _pyproject_data["requires-python"]
index 62602ef4b195fb7ddb1e4e8c33f520414907b359..57e337ac289821149b49248fcf9e2a23e03c4ad6 100644 (file)
@@ -139,8 +139,11 @@ def check_allowed_python_version():
     supported = get_allowed_python_versions()
     this_py = sys.version_info[:2]
     if this_py not in supported:
-        log.error(f"Unsupported python version detected. Supported versions: {supported}")
-        sys.exit(1)
+        log.warning("*" * 80)
+        log.warning(f"Unsupported Python version detected: {this_py}.")
+        log.warning("The build will probably fail.")
+        log.warning(f"Supported versions: {supported}")
+        log.warning("*" * 80)
 
 
 qt_src_dir = ''
index 35d6dd1c3cc20ba22fdb15206c4a64cc93599a77..04403ba0fb947d8393dfb1df17978476cfcac641 100644 (file)
@@ -236,7 +236,7 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False):
 
     # Some libraries specific to Linux/Android from 6.8
     # eg: the libav* libraries are required for the multimedia module
-    if config.is_internal_pyside_build() and sys.platform != "darwin":
+    if config.is_internal_pyside_build() and (sys.platform != "darwin" or is_android):
         qt_multimedia_filters = [f"lib{lib}*.so*" for lib in PYSIDE_MULTIMEDIA_LIBS]
         copydir("{qt_lib_dir}", destination_qt_dir / "lib",
                 _filter=qt_multimedia_filters,
index eaab7acb6780ef73698d1615002f2bee17409557..073f390a43b0d85b06e57e4eeee55da58dd98702 100644 (file)
@@ -5,6 +5,7 @@ from __future__ import annotations
 import functools
 import os
 import tempfile
+import platform
 
 from pathlib import Path
 
@@ -12,7 +13,8 @@ from ..log import log
 from ..config import config
 from ..options import OPTION
 from ..utils import (copydir, copyfile, copy_qt_metatypes,
-                     download_and_extract_7z, filter_match, makefile, in_coin)
+                     download_and_extract_7z, filter_match, makefile, in_coin,
+                     coin_job_id)
 from .. import PYSIDE, SHIBOKEN, PYSIDE_WINDOWS_BIN_TOOLS, PYSIDE_MULTIMEDIA_LIBS
 
 
@@ -194,11 +196,11 @@ def prepare_packages_win32(pyside_build, _vars):
     if config.is_internal_shiboken_module_build():
         # The C++ std library dlls need to be packaged with the
         # shiboken module, because libshiboken uses C++ code.
-        copy_msvc_redist_files(destination_dir)
+        download_qt_dependency_dlls(_vars, destination_dir, msvc_redist)
 
     if config.is_internal_pyside_build() or config.is_internal_shiboken_generator_build():
         copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars)
-        copy_msvc_redist_files(destination_dir)
+        download_qt_dependency_dlls(_vars, destination_dir, msvc_redist)
 
 
 # MSVC redistributable file list.
@@ -216,31 +218,20 @@ msvc_redist = [
 ]
 
 
-def copy_msvc_redist_files(destination_dir):
-    if not in_coin():
-        log.info("Qt dependency DLLs (MSVC redist) will not be copied.")
-        return
-
-    # Make a directory where the files should be extracted.
-    if not destination_dir.exists():
-        destination_dir.mkdir(parents=True)
+def get_cache_dir():
+    """Return the name of a cache directory for storing artifacts for repetitive
+       runs of setup.py depending on COIN_UNIQUE_JOB_ID."""
+    job_id = coin_job_id()
+    dir = tempfile.gettempdir() + os.sep + "pyside" + job_id[0:job_id.find('-')]
+    return Path(dir)
 
-    # Copy Qt dependency DLLs (MSVC) from PATH when building on Qt CI.
-    paths = os.environ["PATH"].split(os.pathsep)
-    for path in paths:
-        try:
-            for f in Path(path).glob("*140*.dll"):
-                if f.name in msvc_redist:
-                    copyfile(f, Path(destination_dir) / f.name)
-                    msvc_redist.remove(f.name)
-            if not msvc_redist:
-                break
-        except WindowsError:
-            continue
 
-    if msvc_redist:
-        msg = "The following Qt dependency DLLs (MSVC redist) were not found: {msvc_redist}"
-        raise FileNotFoundError(msg)
+def download_and_extract_7z_to_cache(url, cache_dir):
+    """Download the artifacts to the cache directory unless it exists."""
+    if not cache_dir.is_dir():
+        cache_dir.mkdir(parents=True)
+    if not list(cache_dir.glob("*.dll")):
+        download_and_extract_7z(url, cache_dir)
 
 
 def download_qt_dependency_dlls(_vars, destination_qt_dir, artifacts):
@@ -249,17 +240,19 @@ def download_qt_dependency_dlls(_vars, destination_qt_dir, artifacts):
         log.info("Qt dependency DLLs will not be downloaded and extracted.")
         return
 
-    with tempfile.TemporaryDirectory() as temp_path:
-        redist_url = "https://download.qt.io/development_releases/prebuilt/vcredist/"
-        zip_file = "pyside_qt_deps_681_64_2022.7z"
-        try:
-            download_and_extract_7z(redist_url + zip_file, temp_path)
-        except Exception as e:
-            log.warning(f"Download failed: {type(e).__name__}: {e}")
-            log.warning("download.qt.io is down, try with mirror")
-            redist_url = "https://master.qt.io/development_releases/prebuilt/vcredist/"
-            download_and_extract_7z(redist_url + zip_file, temp_path)
-        copydir(temp_path, destination_qt_dir, _filter=artifacts, recursive=False, _vars=_vars)
+    cache_dir = get_cache_dir()
+    redist_url = "https://download.qt.io/development_releases/prebuilt/vcredist/"
+    zip_file = "pyside_qt_deps_684_64_2022.7z"
+    if platform.machine() == "ARM64":
+        zip_file = "pyside_qt_deps_690_arm_2022.7z"
+    try:
+        download_and_extract_7z_to_cache(redist_url + zip_file, cache_dir)
+    except Exception as e:
+        log.warning(f"Download failed: {type(e).__name__}: {e}")
+        log.warning("download.qt.io is down, try with mirror")
+        redist_url = "https://master.qt.io/development_releases/prebuilt/vcredist/"
+        download_and_extract_7z_to_cache(redist_url + zip_file, cache_dir)
+    copydir(cache_dir, destination_qt_dir, _filter=artifacts, recursive=False, _vars=_vars)
 
 
 def copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars):
@@ -444,5 +437,6 @@ def copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars):
                  destination_qt_dir,
                  _vars=_vars)
 
-    if copy_clang:
+    if copy_clang or platform.machine() == "ARM64":
+        # Qt CI is using dynamic libclang with arm config.
         pyside_build.prepare_standalone_clang(is_win=True)
index 01c05f144a292e9d47e2c59015acb6dac59481e3..b1af14a4d43391667bdd00906b93b874e519536d 100644 (file)
@@ -37,6 +37,7 @@ Jobs             Number of jobs to be run simultaneously
 Modules          Comma separated list of modules to be built
                  (for --module-subset=)
 Python           Python executable (Use python_d for debug builds on Windows)
+Wheel            (boolean) Install via wheels instead of running setup.py install
 
 Arbitrary keys can be defined and referenced by $(name):
 
@@ -76,6 +77,7 @@ GENERATOR_KEY = 'Generator'
 JOBS_KEY = 'Jobs'
 MODULES_KEY = 'Modules'
 PYTHON_KEY = 'Python'
+WHEEL_KEY = 'Wheel'
 
 DEFAULT_MODULES = "Core,Gui,Widgets,Network,Test,Qml,Quick,Multimedia,MultimediaWidgets"
 DEFAULT_CONFIG_FILE = f"Modules={DEFAULT_MODULES}\n"
@@ -282,10 +284,34 @@ def get_config_file(base_name) -> Path:
     return config_file
 
 
-def build(target: str):
-    """Run configure and build steps"""
-    start_time = time.time()
+def pip_list():
+    """List installed packages from the output lines of pip (shiboken6  6.9.0a1)."""
+    result = []
+    pattern = re.compile(r"^([^\s]+)\s+\d.*$")
+    for line in run_process_output(["pip", "list"]):
+        match = pattern.search(line)
+        if match:
+            result.append(match.group(1))
+    return result
+
 
+def uninstall_pyside():
+    """Uninstall all PySide related packages."""
+    packages = []
+    for p in pip_list():
+        if "shiboken" in p or "PySide" in p:
+            packages.append(p)
+    if not packages or opt_dry_run:
+        return
+    yes = "Y\n" * len(packages)
+    cmd = ["pip", "uninstall"] + packages
+    with subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
+                          stderr=subprocess.PIPE, text=True) as process:
+        print(process.communicate(input=yes)[0])
+
+
+def run_build(target: str):
+    """Run configure and build steps"""
     arguments = []
     acceleration = read_acceleration_config()
     if not IS_WINDOWS and acceleration == Acceleration.INCREDIBUILD:
@@ -323,8 +349,33 @@ def build(target: str):
 
     execute(arguments)
 
-    elapsed_time = int(time.time() - start_time)
-    print(f'--- Done({elapsed_time}s) ---')
+
+def build(skip_install: bool):
+    """Run configure and build steps"""
+    start_time = time.time()
+    use_wheel = read_bool_config(WHEEL_KEY)
+    target = "build" if use_wheel or skip_install else "install"
+    run_build(target)
+    build_time_stamp = time.time()
+    elapsed_time = int(build_time_stamp - start_time)
+    print(f"--- Build done({elapsed_time}s) ---")
+    if not use_wheel or skip_install:
+        return
+    print()
+    wheel_dir = Path.cwd() / "dist"
+    if not opt_dry_run:
+        for w in wheel_dir.glob("*.whl"):
+            w.unlink()
+    create_wheel_cmd = [read_config_python_binary(), "create_wheels.py", "--no-examples"]
+    execute(create_wheel_cmd)
+    install_cmd = ["pip", "install", "--force-reinstall"]
+    for w in wheel_dir.glob("*.whl"):
+        if not w.name.startswith("pyside6-"):
+            install_cmd.append(os.fspath(w))
+    execute(install_cmd)
+    install_time_stamp = time.time()
+    elapsed_time = int(install_time_stamp - build_time_stamp)
+    print(f"--- Install done({elapsed_time}s) ---")
 
 
 def build_base_docs():
@@ -373,6 +424,8 @@ def create_argument_parser(desc):
                         help='Run tests')
     parser.add_argument('--Documentation', '-D', action='store_true',
                         help='Run build_base_docs')
+    parser.add_argument('--uninstall', '-U', action='store_true',
+                        help='Uninstall packages')
     parser.add_argument('--version', '-v', action='version', version='%(prog)s 1.0')
     parser.add_argument('--verbose', '-V', action='store_true',
                         help='Turn off --quiet specified in build arguments')
@@ -411,7 +464,8 @@ if __name__ == '__main__':
         build_mode = BuildMode.RECONFIGURE
 
     if build_mode == BuildMode.NONE and not (options.clean or options.reset or options.pull
-                                             or options.Documentation or options.test):
+                                             or options.uninstall or options.Documentation
+                                             or options.test):
         argument_parser.print_help()
         sys.exit(0)
 
@@ -435,6 +489,9 @@ if __name__ == '__main__':
 
     base_dir = Path.cwd().name
 
+    if options.uninstall:
+        uninstall_pyside()
+
     if options.clean:
         run_git(['clean', '-dxf'])
 
@@ -445,8 +502,7 @@ if __name__ == '__main__':
         run_git(['pull', '--rebase'])
 
     if build_mode != BuildMode.NONE:
-        target = 'build' if options.no_install else 'install'
-        build(target)
+        build(options.no_install)
 
     if options.Documentation:
         build_base_docs()
index 29f2545d08dd864f99b95c573fec8fd38b685279..eb0c8b0b673f170b77aaf603088831e982b456b4 100644 (file)
@@ -1123,8 +1123,12 @@ def copy_qt_metatypes(destination_qt_dir, _vars):
             recursive=False, _vars=_vars, force_copy_symlinks=True)
 
 
+def coin_job_id():
+    return os.environ.get("COIN_UNIQUE_JOB_ID", None)
+
+
 def in_coin():
-    return os.environ.get('COIN_UNIQUE_JOB_ID', None) is not None
+    return coin_job_id() is not None
 
 
 def parse_modules(modules: str) -> str:
index 7ee04a7be259b07c5a1659c3ef5268c90be61dac..e9b936f100504d816a44813b9fe960f733c6910b 100644 (file)
@@ -539,6 +539,11 @@ def module_QtQml() -> ModuleData:
     data.qml.extend(_qml)
 
     data.extra_files.append("qmllint*")
+    # adds qmllint plugins
+    json_data_qmllint = get_module_json_data("QmlCompiler")
+    qml_lint_plugins = get_module_plugins(json_data_qmllint)
+    data.plugins += qml_lint_plugins
+
     data.extra_files.append("qmlformat*")
     data.extra_files.append("qmlls*")
 
@@ -727,6 +732,8 @@ def module_Qt3DInput() -> ModuleData:
 
 def module_Qt3DLogic() -> ModuleData:
     data = ModuleData("3DLogic", qml=["Qt3D/Logic"])
+    json_data = get_module_json_data("3DLogic")
+    data.plugins = get_module_plugins(json_data)
 
     return data
 
@@ -761,10 +768,11 @@ def module_QtQuick3D() -> ModuleData:
         "libQt63DQuick",
         "libQt63DQuickAnimation",
         "libQt63DQuickExtras",
-        "libQt63DQuickExtras",
+        "libQt63DQuickLogic",
         "libQt63DQuickInput",
         "libQt63DQuickRender",
         "libQt63DQuickScene2D",
+        "libQt63DQuickScene3D",
         "libQt6Quick3DXr",
     ]
 
@@ -1032,6 +1040,7 @@ def module_QtVirtualKeyboard() -> ModuleData:
     data = ModuleData("VirtualKeyboard")
     data.plugins.append("virtualkeyboard")
     data.qtlib.append("libQt6VirtualKeyboardSettings")
+    data.qtlib.append("libQt6VirtualKeyboardQml")
 
     return data
 
index 478bdfdf717eef310154aa28da27d10b83403b21..ad4751d25fe5996ed2a63fca6ee49b3668a6cd9f 100644 (file)
@@ -1,6 +1,6 @@
 product_dependency:
   ../../qt/qt5:
-    ref: "480041bb0bfd400f29dd49facfaa924dacac5c03"
+    ref: "af7939f2df81369a4a6501c9b41e6e68278269eb"
 dependency_source: supermodule
 dependencies: [
       "../../qt/qt3d",
diff --git a/coin/fetch_libclang_arm64.ps1 b/coin/fetch_libclang_arm64.ps1
new file mode 100644 (file)
index 0000000..fcb4d71
--- /dev/null
@@ -0,0 +1,8 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+# Download the file
+
+wget https://master.qt.io/development_releases/prebuilt/libclang/libclang-release_19.1.0-based-windows-vs2022_arm64.7z -o libclang.7z
+# Unzip the contents to /home/qt
+7z x libclang.7z -o/utils
+Remove-Item libclang.7z
index c99e3357de80baca9c2ecdff048d406b4e34fc58..5de4a32619f7b7ec9838c80579ae1a8edbb20a0f 100644 (file)
@@ -2,7 +2,7 @@
 # Copyright (C) 2024 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 # Download the file
-wget -q https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_18.1.7-based-linux-Debian-11.6-gcc10.2-arm64.7z
+wget -q https://master.qt.io/development_releases/prebuilt/libclang/libclang-release_18.1.7-based-linux-Debian-11.6-gcc10.2-arm64.7z
 if [ $? -ne 0 ]; then
     echo "Error: Failed to download libclang archive" >&2
     exit 1
index 7663014e2368cb67fc4fbf890d8b654747fb7f38..949eecad3b0cc15f868b2d73d68af2bd13641eac 100644 (file)
@@ -46,6 +46,33 @@ instructions:
           - condition: property
             property: host.os
             equals_value: Windows
+          - condition: property
+            property: host.arch
+            not_equals_value: AARCH64
+    - type: EnvironmentVariable
+      variableName: PYTHON3_PATH
+      variableValue: "{{ index .Env \"PYTHON3.11.9-64_PATH\"}}"
+      enable_if:
+        condition: and
+        conditions:
+          - condition: property
+            property: host.os
+            equals_value: Windows
+          - condition: property
+            property: host.arch
+            equals_value: AARCH64
+    - type: EnvironmentVariable
+      variableName: TARGET_ARCHITECTURE
+      variableValue: arm64
+      enable_if:
+        condition: and
+        conditions:
+          - condition: property
+            property: target.arch
+            equals_value: AARCH64
+          - condition: property
+            property: host.os
+            equals_value: Windows
     - type: EnvironmentVariable
       variableName: TARGET_ARCHITECTURE
       variableValue: amd64_x86
@@ -123,7 +150,7 @@ instructions:
          equals_value: Windows
     - type: PrependToEnvironmentVariable
       variableName: PATH
-      variableValue: "{{.Env.PYTHON3_PATH}}/bin:"
+      variableValue: "{{.Env.PYTHON3_PATH}}:"
       enable_if:
          condition: property
          property: host.os
@@ -170,6 +197,18 @@ instructions:
          condition: property
          property: target.compiler
          equals_value: ICC_18
+    - type: EnvironmentVariable
+      variableName: PYTHON3_PATH
+      variableValue: "{{ index .Env \"PYTHON3.10.0-64_PATH\"}}"
+      enable_if:
+          condition: and
+          conditions:
+            - condition: property
+              property: host.os
+              equals_value: Windows
+            - condition: property
+              property: host.arch
+              equals_value: X86_64
     - type: EnvironmentVariable
       variableName: ICC64_18_PATH # Seems a bit hard to maintain
       variableValue: /opt/intel/compilers_and_libraries_2018.1.163/linux/bin/intel64:/opt/intel/bin
@@ -236,6 +275,21 @@ instructions:
               equals_value: AARCH64
       userMessageOnFailure: >
           Failed to download libclang from Qt servers
+    - type: ExecuteCommand
+      command: ["powershell", "-ExecutionPolicy", "Bypass", "-File", "coin\\fetch_libclang_arm64.ps1"]
+      maxTimeInSeconds: 14400
+      maxTimeBetweenOutput: 1200
+      enable_if:
+          condition: and
+          conditions:
+            - condition: property
+              property: host.os
+              equals_value: Windows
+            - condition: property
+              property: host.arch
+              equals_value: AARCH64
+      userMessageOnFailure: >
+          Failed to download libclang from Qt servers
     - type: EnvironmentVariable
       variableName: LLVM_INSTALL_DIR
       variableValue: "/home/qt/libclang"
@@ -248,6 +302,18 @@ instructions:
           - condition: property
             property: host.os
             equals_value: Linux
+    - type: EnvironmentVariable
+      variableName: LLVM_INSTALL_DIR
+      variableValue: "\\utils\\libclang"
+      enable_if:
+        condition: and
+        conditions:
+          - condition: property
+            property: target.arch
+            equals_value: AARCH64
+          - condition: property
+            property: host.os
+            equals_value: Windows
     - type: EnvironmentVariable
       variableName: interpreter
       variableValue: "python3.11"
index bece46b2bec3150b96db6602b71a5fdce759be8a..9bd113a2ea68902476294a59f8b501612fe8fd13 100644 (file)
@@ -47,6 +47,18 @@ instructions:
         condition: property
         property: host.os
         equals_value: Windows
+  - type: EnvironmentVariable
+    variableName: PYSIDE_SIGNING_DIR
+    variableValue: "{{.AgentWorkingDir}}\\pyside\\{{.Env.TESTED_MODULE_COIN}}\\build\\qfpa-p3.11\\package_for_wheels"
+    enable_if:
+        condition: and
+        conditions:
+          - condition: property
+            property: host.os
+            equals_value: Windows
+          - condition: property
+            property: host.arch
+            equals_value: AARCH64
   - type: ExecuteCommand
     command: "{{.Env.interpreter}} -m pip install -r requirements-coin.txt --user"
     maxTimeInSeconds: 14400
@@ -83,7 +95,7 @@ instructions:
     userMessageOnFailure: >
          Failed to install requirements-coin.txt dependencies on Windows
   - type: ExecuteCommand
-    command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=BUILD"
+    command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch={{.Env.HOST_ARCH_COIN}}  --targetArch={{.Env.TARGET_ARCH_COIN}} --phase=BUILD"
     maxTimeInSeconds: 14400
     maxTimeBetweenOutput: 600
     enable_if:
index 7399ccb42c18745f169974090cddf4d9a7793f8b..521503c396948168f6f68ad2a9f9eb0ddec7d43e 100644 (file)
@@ -86,7 +86,7 @@ instructions:
     userMessageOnFailure: >
          Failed to install requirements-coin.txt on Windows
   - type: ExecuteCommand
-    command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=c:\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}"
+    command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=c:\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch={{.Env.HOST_ARCH_COIN}}  --targetArch={{.Env.TARGET_ARCH_COIN}}"
     maxTimeInSeconds: 14400
     maxTimeBetweenOutput: 600
     enable_if:
index afab83c701fbcbd00084a4b4210809087d96582d..5e16aef5d1212db47e13608669435f02e51a0699 100644 (file)
@@ -31,7 +31,7 @@ instructions:
     userMessageOnFailure: >
          Failed to remove pyside-setup dir
   - type: InstallBinaryArchive
-    relativeStoragePath: "{{.Env.MODULE_ARTIFACTS_RELATIVE_STORAGE_PATH}}/artifacts.tar.gz"
+    relativeStoragePath: "{{.Env.MODULE_ARTIFACTS_RELATIVE_STORAGE_PATH}}/artifacts.tar.zst"
     directory: "pyside"
     maxTimeInSeconds: 1200
     maxTimeBetweenOutput: 1200
index 176a6d22591e5f9f404fcd4e1dd56e738f7d17f4..52c8211e08ecdd7e253151af8ce9f6331ece49bb 100644 (file)
@@ -144,7 +144,7 @@ def remove_variables(vars):
             del os.environ[env_var]
 
 
-def setup_virtualenv(python, exe, env, pip, log):
+def setup_virtualenv(python, exe, env, pip, log, ci):
     # Within Ubuntu 24.04 one can't install anything with pip to outside of
     # virtual env. Trust that we already have proper virtualenv installed.
     if os.environ.get("HOST_OSVERSION_COIN") != "ubuntu_24_04":
@@ -156,7 +156,10 @@ def setup_virtualenv(python, exe, env, pip, log):
     env_path = Path(str(site.USER_BASE)) / "bin"
     v_env = env_path / "virtualenv"
     if sys.platform == "win32":
-        env_path = os.path.join(site.USER_BASE, "Scripts")
+        if ci.TARGET_ARCH == "aarch64":
+            env_path = os.path.join(site.USER_BASE, "Python311-arm64", "Scripts")
+        else:
+            env_path = os.path.join(site.USER_BASE, "Scripts")
         v_env = os.path.join(env_path, "virtualenv.exe")
     try:
         run_instruction([str(v_env), "--version"], "Using default virtualenv")
@@ -191,7 +194,7 @@ def call_setup(python_ver, ci, phase, log, buildnro=0):
         python = Path(get_env_or_raise("PYTHON3_PATH")) / "python.exe"
 
     if phase == "BUILD":
-        setup_virtualenv(python, exe, env, pip, log)
+        setup_virtualenv(python, exe, env, pip, log, ci)
     elif phase == "TEST":
 
         if ci.HOST_OS == "MacOS" and ci.HOST_ARCH == "ARM64":
@@ -201,7 +204,7 @@ def call_setup(python_ver, ci, phase, log, buildnro=0):
                 [pip, "install", "-r", "requirements.txt"], "Failed to install dependencies"
             )
         else:
-            setup_virtualenv(python, exe, env, pip, log)
+            setup_virtualenv(python, exe, env, pip, log, ci)
             # Install distro to replace missing platform.linux_distribution() in python3.8
             run_instruction([pip, "install", "distro"], "Failed to install distro")
 
index 450365ca8fa6493f5619f5181663098f1bf1d044..471c840356dbc86e4604e221b27726a297f6a038 100644 (file)
@@ -25,9 +25,6 @@ accept_configuration:
         - condition: property  # Windows on Arm
           property: target.arch
           not_equals_value: ARM64
-        - condition: property  # Windows on Arm host build
-          property: target.arch
-          not_equals_value: AARCH64
         - condition: property
           property: features
           not_contains_value: DebianPackaging
index 32a89a6a7fbead885b886537f0fb4eca470defb9..55d02928bbf65b20f12f6618babffebae9f22a14 100644 (file)
@@ -149,8 +149,8 @@ def get_platform_tag() -> str:
         # We know the CI builds universal2 wheels
         _tag = f"macosx_{target}_universal2"
     elif _os == "win32":
-        win_arch = platform.architecture()[0]
-        msvc_arch = "x86" if win_arch.startswith("32") else "amd64"
+        win_arch = platform.machine()
+        msvc_arch = "arm64" if win_arch.startswith("ARM64") else "amd64"
         _tag = f"win_{msvc_arch}"
 
     return _tag
diff --git a/doc/changelogs/changes-6.8.3 b/doc/changelogs/changes-6.8.3
new file mode 100644 (file)
index 0000000..05b90ab
--- /dev/null
@@ -0,0 +1,52 @@
+Qt for Python 6.8.3 is a bug-fix release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+*                                  PySide6                                 *
+****************************************************************************
+
+ - [PYSIDE-1735]  Most enumerations are now fully qualified in documentation
+                  and translated code snippets. Enum values automatically
+                  converted to "None_" are also correctly documented.
+ - [PYSIDE-2846]  The type annotation of the Slot() decorator has been fixed.
+ - [PYSIDE-2997]  Type hints: The signature of QMessageBox.information()
+                  has been fixed.
+ - [PYSIDE-3001]  QtAsyncio: The error messages printed on an exceptions
+                  in tasks have been improved.
+ - [PYSIDE-3002]  QPaintEngine's virtual functions taking a
+                  C-style array of geometry primitives have been fixed.
+ - [PYSIDE-3003]  Type hints: A bug causing overloads to be omitted has
+                  been fixed.
+ - [PYSIDE-3012]  Type hints: Since using Callable, Iterable and Sequence from
+                  typing is deprecated, they are imported from collections.abc.
+ - [PYSIDE-3012]  Type hints: Object inheritance has been removed from classes.
+ - [PYSIDE-3013]  QFont.Tag(str), QFont.Tag.fromString(), QFont.Tag.fromValue()
+                  have been added.
+ - [PYSIDE-3014]  QAbstractSpinBox.fixup() has been fixed.
+ - [PYSIDE-3017]  The overloads of QCanDbcFileParser.parse() have been fixed.
+ - [PYSIDE-3020]  A bug disconnecting a string-based connection by passing a
+                  callable has been fixed.
+ - [QTBUG-72968]  The type of the "result" parameter of the native event
+                  filters has been changed to "qintptr" for Qt 6.
+
+****************************************************************************
+*                                  Shiboken6                               *
+****************************************************************************
+
+ - Warnings about rejected functions/fields have been removed or redirected
+   to the log files.
+ - [PYSIDE-2701]  The size of the generated modules has been reduced by
+                  factoring out common code from the code generated  for
+                   virtual functions.
diff --git a/doc/changelogs/changes-6.9.0 b/doc/changelogs/changes-6.9.0
new file mode 100644 (file)
index 0000000..f706b20
--- /dev/null
@@ -0,0 +1,98 @@
+Qt for Python 6.9.0 is a minor release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+*                                  PySide6                                 *
+****************************************************************************
+
+ - The usage of macros from CPython (limited API) has been reduced in favor
+   of calling functions directly.
+ - Support for std::chrono::milliseconds has been added.
+ - Windows ARM64 was added as a new supported platform (technical preview)
+ - [PYSIDE-862]   Support for QtRemoteObjects has been extended.
+ - [PYSIDE-1057]  A memory leak connecting to signals has been fixed.
+ - [PYSIDE-1277]  A bug connecting signals by QMetaMethod has been fixed.
+ - [PYSIDE-1735]  Most enumerations are now fully qualified in documentation
+                  and translated code snippets. Enum values automatically
+                  converted to "None_" are also correctly documented.
+ - [PYSIDE-2714]  Qt Creator 17.x+ and PySide tools now support projects that
+                  have a pyproject.toml instead of a *.pyproject
+ - [PYSIDE-2846]  The type annotation of the Slot() decorator has been fixed.
+ - [PYSIDE-2966]  A bug making it possible to instantiate non-constructible
+                  classes and namespaces has been fixed.
+ - [PYSIDE-2891]  A bug in signature handling affecting 32bit builds
+                  has been fixed.
+ - [PYSIDE-2958]  Building against unsupported python versions has been
+                  enabled.
+ - [PYSIDE-2997]  Type hints: The signature of QMessageBox.information()
+                  has been fixed.
+ - [PYSIDE-3001]  QtAsyncio: The error messages printed on an exceptions
+                  in tasks have been improved.
+ - [PYSIDE-3002]  QPaintEngine's virtual functions taking a
+                  C-style array of geometry primitives have been fixed.
+ - [PYSIDE-3003]  Type hints: A bug causing overloads to be omitted has
+                  been fixed.
+ - [PYSIDE-3004]  A crash in slots when receiving an object-type parameter
+                  passed by const-ref has been fixed.
+ - [PYSIDE-3005]  A bug affecting
+                  QOpenGLShaderProgram.setUniformValueArray(int,float*,int,int)
+                  has been fixed.
+ - [PYSIDE-3012]  Type hints: Since using Callable, Iterable and Sequence from
+                  typing is deprecated, they are imported from collections.abc.
+ - [PYSIDE-3012]  Type hints: Object inheritance has been removed from classes.
+ - [PYSIDE-3013]  QFont.Tag(str), QFont.Tag.fromString(), QFont.Tag.fromValue()
+                  have been added.
+ - [PYSIDE-3014]  QAbstractSpinBox.fixup() has been fixed.
+ - [PYSIDE-3017]  The overloads of QCanDbcFileParser.parse() have been fixed.
+ - [PYSIDE-3020]  A bug disconnecting a string-based connection by passing a
+                  callable has been fixed.
+ - [QTBUG-72968]  The type of the "result" parameter of the native event
+                  filters has been changed to "qintptr" for Qt 6.
+ - [QTBUG-108199] PySide has been adapted to the deprecation of Qt::TimeSpec
+                  in Qt.
+
+****************************************************************************
+*                                  Shiboken6                               *
+****************************************************************************
+
+ - Helper class AutoArrayPointer has been renamed to ArrayPointer. A
+   convenience typedef is provided.
+ - It is now possible to use file snippets for XML template content.
+ - Warnings about rejected functions/fields have been removed or redirected
+   to log files to reduce clutter.
+ - An additional log file, mjb_shiboken.log has been introduced which
+   contains informational messages about the types encountered.
+ - [PYSIDE-454]   It is now possible to exclude smart pointer instantiations
+                  from underlying base modules to prevent symbol clashes.
+ - [PYSIDE-2701]  The size of the generated modules has been reduced by
+                  applying several optimizations to the code generated for
+                  virtual functions.
+ - [PYSIDE-2701]  Function modifications are now correctly inherited also in
+                  case of multiple inheritance.
+ - [PYSIDE-2986]  shiboken6 can now generate doc strings for classes from
+                  injected documentation snippets.
+ - [PYSIDE-3004]  The detection of copy constructibility of value type
+                  classes has been improved using functionality from Clang.
+                  New typesystem attributes have been introduced to enable
+                  overriding the detection.
+ - [PYSIDE-3004]  The handling of move only value types has been improved.
+ - [PYSIDE-3004]  A documentation page about value versus objects has been
+                  added.
+ - [PYSIDE-3004]  Non-default constructible value types can now be
+                  passed by non-const reference.
+ - [PYSIDE-3004]  Warnings about special types are now printed, particularly
+                  about object types that could be value types.
+ - [QTBUG-133704] It is now possible to specify in the type system whether
+                  Qt meta type registration code should be generated for enums.
diff --git a/doc/changelogs/changes-6.9.1 b/doc/changelogs/changes-6.9.1
new file mode 100644 (file)
index 0000000..6f4ef50
--- /dev/null
@@ -0,0 +1,64 @@
+Qt for Python 6.9.1 is a bug-fix release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+*                                  PySide6                                 *
+****************************************************************************
+
+ -  [PYSIDE-841] An example showing an audio graph using Qt Graphs has been
+                 added.
+ - [PYSIDE-2193] A crash retrieving a Python type QObject property
+                 (encapsulated in a QVariant) been fixed.
+ - [PYSIDE-3052] class QMessageLogger has been added for handling debug,
+                 info, warning, critical, and fatal messages,
+                 including support for QLoggingCategory.
+ - [PYSIDE-3012] type hints: The type signature for the Property class
+                 has been fixed.
+ - [PYSIDE-3021] type-hints: The QMessagebox annotations has been fixed.
+ - [PYSIDE-3029] type-hints: The hints for properties on __init__ functions
+                 has been fixed.
+ - [PYSIDE-3041] type hints: The str parameter of  QLineEdit.setText() has
+                 been made optional.
+ - [PYSIDE-3050] type hints: Type signature for
+                 QProgressDialog.setCancelButton has been fixed.
+ - [PYSIDE-3055] type hints: Type signature for QTabBar.setTabButton has
+                 been fixed.
+ - [PYSIDE-3056] type hints: Type signature for qtTrId has been fixed.
+ - [PYSIDE-3057] type hints: Type signature for QTreeWidget.setItemWidget
+                 has been fixed.
+ - [PYSIDE-3058] type hints: Return value for QTreeWidget.topLevelItem and
+                 QTreeWidget.takeTopLevelItem has been fixed.
+ - [PYSIDE-3059] type hints: The type signature for QObject class has been
+                 fixed.
+ - [PYSIDE-3061] The building of .pyi files in debug mode on Windows has
+                 been fixed.
+ - [PYSIDE-3067] A crash when entering a Qt message handler with a Python
+                 error set has been fixed.
+ - [PYSIDE-3069] A crash retrieving a QGraphicsProxyObject from a QVariant
+                 has been fixed.
+ - [PYSIDE-3078] type hints: The parent widget parameter of the
+                 QInputDialog get() methods has been made optional.
+ - [PYSIDE-3087] The dependency of pyside6-project on tomlkit has been
+                 removed.
+ - [PYSIDE-3089] An error in pyside6-metaobjectdump when encountering
+                 @Slot(result=None) has been fixed.
+
+****************************************************************************
+*                                  Shiboken6                               *
+****************************************************************************
+
+ - [PYSIDE-3081] A bug in the clang parser causing errors when parsing a
+                 lambda contained in a function parameter default value has
+                 been fixed.
diff --git a/doc/changelogs/changes-6.9.2 b/doc/changelogs/changes-6.9.2
new file mode 100644 (file)
index 0000000..bfbee9a
--- /dev/null
@@ -0,0 +1,59 @@
+Qt for Python 6.9.2 is a bug-fix release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+*                                  PySide6                                 *
+****************************************************************************
+
+ - [PYSIDE-1612] Android Deployment: Installing packages is no longer forced.
+ - [PYSIDE-1612] Desktop Deployment: A warning for Qt resource files has
+                 been fixed.
+ - [PYSIDE-1612] Desktop Deployment: Nuitka has been upgraded to 2.7.11.
+ - [PYSIDE-2846] type hints: The mypy version has been increased.
+ - [PYSIDE-2846] type hints: Signature warnings about QObject properties of
+                 unknown type passed to the constructor have been fixed.
+ - [PYSIDE-2938] The MSVC runtime libraries bundled in Windows wheels
+                 have been updated.
+ - [PYSIDE-3095] pyside6-project lupdate now supports specifying
+                 subdirectories in .ts file names.
+ - [PYSIDE-3115] A bug affecting QVariant conversion of Python classes
+                 inheriting QGraphicsProxyObject has been fixed.
+ - [PYSIDE-3119] A SECURITY.md document required for Github has been added.
+ - [PYSIDE-3124] Documentation about thread affinity has been added
+                 to the Signals and Slot tutorial.
+ - [PYSIDE-3127] A bug occurring when choosing a camera in the camera
+                 example has been fixed.
+ - [PYSIDE-3132] A crash calling setItemDelegateForColumn/Row()
+                 repeatedly has been fixed.
+ - [PYSIDE-3133] A crash on conversion errors when parsing keyword
+                 arguments has been fixed.
+ - [PYSIDE-3135] type hints: typing.Self is no longer modified in
+                 versions < 3.11.
+ - [PYSIDE-3139] type hints: The return types of QGuiApplication.screenAt()
+                 and QGuiApplication.modalWindow() have been fixed.
+ - [PYSIDE-3146] Deployment: Values generated into pysidedeploy.spec are
+                 now sorted.
+ - [PYSIDE-3147] Initial adaptations for the upcoming Python version 3.14
+                 have been done.
+ - [PYSIDE-3148] A memory corruption occurring when connecting several
+                 signals to one non-QObject receiver has been fixed.
+
+****************************************************************************
+*                                  Shiboken6                               *
+****************************************************************************
+
+ - [PYSIDE-3105] Cross compilation support has been improved.
+ - [PYSIDE-3144] A crash occurring when no source class can be found for
+                 typedef typesystem entries has been fixed.
index d6792357a0d718d16c3264df77d51eb42578e5cd..db23991823c2e509f96f3925e84e21a4dbc99d38 100644 (file)
@@ -39,7 +39,7 @@ class MainSlice(QPieSlice):
 class DonutBreakdownChart(QChart):
     def __init__(self, parent=None):
         super().__init__(QChart.ChartTypeCartesian,
-                         parent, Qt.WindowFlags())
+                         parent, Qt.WindowFlags(0))
         self.main_series = QPieSeries()
         self.main_series.setPieSize(0.7)
         self.addSeries(self.main_series)
index 0858007f1d0abb39b9fa9bb581ad3ff199d5d08a..f02c1b88db585310b1e40d181057fec471cb1ab5 100644 (file)
@@ -11,7 +11,7 @@ from PySide6.QtGui import QPen
 
 class Chart(QChart):
     def __init__(self, parent=None):
-        super().__init__(QChart.ChartTypeCartesian, parent, Qt.WindowFlags())
+        super().__init__(QChart.ChartTypeCartesian, parent, Qt.WindowFlags(0))
         self._timer = QTimer()
         self._series = QSplineSeries(self)
         self._titles = []
index fe559a64eb094bdf3d5cd36c0f24a513ac896797..f1d375c8bdb57c9ec0e631561c779f56d465b06a 100644 (file)
@@ -58,7 +58,7 @@ class TestChart(QMainWindow):
         self._axis_x.setRange("Jan", "Jun")
 
         self._axis_y = QValueAxis()
-        self.chart.addAxis(self._axis_x, Qt.AlignLeft)
+        self.chart.addAxis(self._axis_y, Qt.AlignLeft)
         self._line_series.attachAxis(self._axis_y)
         self._bar_series.attachAxis(self._axis_y)
         self._axis_y.setRange(0, 20)
diff --git a/examples/demos/documentviewer/doc/imageviewer.py.rstinc b/examples/demos/documentviewer/doc/imageviewer.py.rstinc
new file mode 100644 (file)
index 0000000..2aeaaf4
--- /dev/null
@@ -0,0 +1,11 @@
+``ImageViewer`` displays images as supported by ``QImageReader``, using
+a QLabel.
+
+In the constructor, we increase the allocation limit of ``QImageReader`` to
+allow for larger photos.
+
+In the ``openFile()`` function, we load the image and determine its size.
+If it is larger than the screen, we downscale it to screen size, maintaining
+the aspect ratio. This calculation has to be done in native pixels, and
+the device pixel ratio needs to be set on the resulting pixmap for it to
+appear crisp.
index 461e3b9d763ad0f47d4c37b95d138c39f8e53057..fe1a4dbf77ad246e6540bb9c580e823c64298657 100644 (file)
@@ -4,6 +4,7 @@
               "main.py",
               "mainwindow.py",
               "mainwindow.ui",
+              "imageviewer/imageviewer.py",
               "jsonviewer/jsonviewer.py",
               "pdfviewer/pdfviewer.py",
               "pdfviewer/zoomselector.py",
diff --git a/examples/demos/documentviewer/imageviewer/imageviewer.py b/examples/demos/documentviewer/imageviewer/imageviewer.py
new file mode 100644 (file)
index 0000000..a2155ac
--- /dev/null
@@ -0,0 +1,173 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+import math
+
+from PySide6.QtWidgets import QLabel
+from PySide6.QtCore import Qt, QDir, QSizeF
+from PySide6.QtGui import (QPixmap, QImageReader, QIcon, QKeySequence,
+                           QGuiApplication, QColorSpace, QPainter, QAction)
+
+from abstractviewer import AbstractViewer
+
+
+def imageFormats():
+    result = []
+    all_formats = QImageReader.supportedImageFormats()
+
+    for format_bytes in all_formats:
+        format_str = bytes(format_bytes).decode("utf-8")  # Convert QByteArray to str
+        if format_str not in ["tif", "cur"]:  # Exclude duplicate/non-existent formats
+            result.append(f"image/{format_str}")
+
+    return result
+
+
+def msgOpen(name, image):
+    description = image.colorSpace().description() if image.colorSpace().isValid() else "unknown"
+    return 'Opened "{0}", {1}x{2}, Depth: {3} ({4})'.format(
+        QDir.toNativeSeparators(name),
+        image.width(),
+        image.height(),
+        image.depth(),
+        description
+    )
+
+
+class ImageViewer(AbstractViewer):
+
+    def __init__(self):
+        super().__init__()
+
+        self.formats = imageFormats()
+        self.uiInitialized.connect(self.setupImageUi)
+        QImageReader.setAllocationLimit(1024)  # MB
+
+    def init(self, file, parent, mainWindow):
+        self.image_label = QLabel(parent)
+        self.image_label.setFrameShape(QLabel.Box)
+        self.image_label.setAlignment(Qt.AlignCenter)
+        self.image_label.setScaledContents(True)
+
+        # AbstractViewer.init(file, self.image_label, mainWindow)
+        super().init(file, self.image_label, mainWindow)
+
+        self.tool_bar = self.addToolBar(self.tr("Images"))
+
+        icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn,
+                               QIcon(":/demos/documentviewer/images/zoom-in.png"))
+        self.zoom_in_act = QAction(icon, "Zoom &In", self)
+        self.zoom_in_act.setShortcut(QKeySequence.StandardKey.ZoomIn)
+        self.zoom_in_act.triggered.connect(self.zoomIn)
+        self.tool_bar.addAction(self.zoom_in_act)
+
+        icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut,
+                               QIcon(":/demos/documentviewer/images/zoom-out.png"))
+        self.zoom_out_act = QAction(icon, "Zoom &Out", self)
+        self.zoom_out_act.setShortcut(QKeySequence.StandardKey.ZoomOut)
+        self.zoom_out_act.triggered.connect(self.zoomOut)
+        self.tool_bar.addAction(self.zoom_out_act)
+
+        icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomFitBest,
+                               QIcon(":/demos/documentviewer/images/zoom-fit-best.png"))
+        self.reset_zoom_act = QAction(icon, "Reset Zoom", self)
+        self.reset_zoom_act.setShortcut(QKeySequence
+                                        (Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_0))
+        self.reset_zoom_act.triggered.connect(self.resetZoom)
+        self.tool_bar.addAction(self.reset_zoom_act)
+
+    def supportedMimeTypes(self):
+        return self.formats
+
+    def clear(self):
+        self.image_label.setPixmap(QPixmap())
+        self.max_scale_factor = self.min_scale_factor = 1
+        self.initial_scale_factor = self.scale_factor = 1
+
+    def setupImageUi(self):
+        self.openFile()
+
+    def openFile(self):
+
+        QGuiApplication.setOverrideCursor(Qt.WaitCursor)
+
+        name = self._file.fileName()
+        reader = QImageReader(name)
+        orig_image = reader.read()
+
+        if orig_image.isNull():
+            self.statusMessage(f"Cannot read file {name}:\n{reader.errorString()}", "open")
+            self.disablePrinting()
+            QGuiApplication.restoreOverrideCursor()
+            return
+
+        self.clear()
+
+        if orig_image.colorSpace().isValid():
+            image = orig_image.convertedToColorSpace(QColorSpace.SRgb)
+        else:
+            image = orig_image
+
+        device_pixel_ratio = self.image_label.devicePixelRatioF()
+        self.image_size = QSizeF(image.size()) / device_pixel_ratio
+
+        pixmap = QPixmap.fromImage(image)
+        pixmap.setDevicePixelRatio(device_pixel_ratio)
+        self.image_label.setPixmap(pixmap)
+
+        target_size = self.image_label.parentWidget().size()
+        if (self.image_size.width() > target_size.width()
+                or self.image_size.height() > target_size.height()):
+            self.initial_scale_factor = min(target_size.width() / self.image_size.width(),
+                                            target_size.height() / self.image_size.height())
+
+        self.max_scale_factor = 3 * self.initial_scale_factor
+        self.min_scale_factor = self.initial_scale_factor / 3
+        self.doSetScaleFactor(self.initial_scale_factor)
+
+        self.statusMessage(msgOpen(name, orig_image))
+        QGuiApplication.restoreOverrideCursor()
+
+        self.maybeEnablePrinting()
+
+    def setScaleFactor(self, scaleFactor):
+        if not math.isclose(self.scale_factor, scaleFactor):
+            self.doSetScaleFactor(scaleFactor)
+
+    def doSetScaleFactor(self, scaleFactor):
+        self.scale_factor = scaleFactor
+        label_size = (self.image_size * self.scale_factor).toSize()
+        self.image_label.setFixedSize(label_size)
+        self.enableZoomActions()
+
+    def zoomIn(self):
+        self.setScaleFactor(self.scale_factor * 1.25)
+
+    def zoomOut(self):
+        self.setScaleFactor(self.scale_factor * 0.8)
+
+    def resetZoom(self):
+        self.setScaleFactor(self.initial_scale_factor)
+
+    def hasContent(self):
+        return not self.image_label.pixmap().isNull()
+
+    def enableZoomActions(self):
+        self.reset_zoom_act.setEnabled(not math.isclose(self.scale_factor,
+                                                        self.initial_scale_factor))
+        self.zoom_in_act.setEnabled(self.scale_factor < self.max_scale_factor)
+        self.zoom_out_act.setEnabled(self.scale_factor > self.min_scale_factor)
+
+    def printDocument(self, printer):
+        if not self.hasContent():
+            return
+
+        painter = QPainter(printer)
+        pixmap = self.image_label.pixmap()
+        rect = painter.viewport()
+        size = pixmap.size()
+        size.scale(rect.size(), Qt.KeepAspectRatio)
+        painter.setViewport(rect.x(), rect.y(), size.width(), size.height())
+        painter.setWindow(pixmap.rect())
+        painter.drawPixmap(0, 0, pixmap)
index a1f4e7447198b14834735070f1d3e670593fc89a..a2de67ada6d5cf8ce04257acd5d2f1178f178d59 100644 (file)
@@ -67,7 +67,7 @@ class PdfViewer(AbstractViewer):
         actionZoomIn.setToolTip("Increase zoom level")
         actionZoomIn.triggered.connect(self.onActionZoomInTriggered)
 
-        icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn,
+        icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut,
                                QIcon(":/demos/documentviewer/images/zoom-out.png"))
         actionZoomOut = self._toolBar.addAction(icon, "Zoom out", QKeySequence.StandardKey.ZoomOut)
         actionZoomOut.setToolTip("Decrease zoom level")
index 0d32cbfeb159304573cd3955bb2a83a79160b0a0..19b9f6a3a7d8eec23589005b5d74336ac114bda1 100644 (file)
@@ -10,6 +10,7 @@ from PySide6.QtCore import (QFileInfo, QMimeDatabase, QTimer)
 from txtviewer.txtviewer import TxtViewer
 from jsonviewer.jsonviewer import JsonViewer
 from pdfviewer.pdfviewer import PdfViewer
+from imageviewer.imageviewer import ImageViewer
 
 
 class DefaultPolicy(Enum):
@@ -29,7 +30,7 @@ class ViewerFactory:
         self._displayWidget = displayWidget
         self._mainWindow = mainWindow
         self._mimeTypes = []
-        for v in [PdfViewer(), JsonViewer(), TxtViewer()]:
+        for v in [PdfViewer(), JsonViewer(), TxtViewer(), ImageViewer()]:
             self._viewers[v.viewerName()] = v
             if v.isDefaultViewer():
                 self._defaultViewer = v
diff --git a/examples/graphs/2d/graphsaudio/GraphsAudio/Main.qml b/examples/graphs/2d/graphsaudio/GraphsAudio/Main.qml
new file mode 100644 (file)
index 0000000..51bf3ef
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtGraphs
+
+ApplicationWindow {
+    visible: true
+    width: 1000
+    height: 800
+    title: "Data from the microphone (" + device_name + ")"
+
+    GraphsView {
+        id: graph
+        anchors.fill: parent
+
+        LineSeries {
+            id: audio_series
+            width: 2
+            color: "#007acc"
+        }
+
+        axisX: ValueAxis  {
+            min: 0
+            max: 2000
+            tickInterval : 500
+            labelFormat: "%g"
+            titleText: "Samples"
+        }
+
+        axisY: ValueAxis  {
+            min: -1
+            max: 1
+            tickInterval : 0.5
+            labelFormat: "%0.1f"
+            titleText: "Audio level"
+        }
+    }
+
+    Connections {
+        target: audio_bridge
+        function onDataUpdated(buffer) {
+            audio_series.clear()
+            for (let i = 0; i < buffer.length; ++i) {
+                audio_series.append(buffer[i])
+            }
+        }
+    }
+}
diff --git a/examples/graphs/2d/graphsaudio/GraphsAudio/qmldir b/examples/graphs/2d/graphsaudio/GraphsAudio/qmldir
new file mode 100644 (file)
index 0000000..cc5408a
--- /dev/null
@@ -0,0 +1,2 @@
+module GraphsAudio
+Main 1.0 Main.qml
diff --git a/examples/graphs/2d/graphsaudio/doc/graphsaudio.rst b/examples/graphs/2d/graphsaudio/doc/graphsaudio.rst
new file mode 100644 (file)
index 0000000..f19b28c
--- /dev/null
@@ -0,0 +1,8 @@
+GraphsAudio Example
+===================
+
+This example shows the drawing of dynamic data (microphone input) using QtGraphs and Qml.
+
+.. image:: graphsaudio.webp
+   :width: 400
+   :alt: GraphsAudio Screenshot
diff --git a/examples/graphs/2d/graphsaudio/doc/graphsaudio.webp b/examples/graphs/2d/graphsaudio/doc/graphsaudio.webp
new file mode 100644 (file)
index 0000000..bb57b18
Binary files /dev/null and b/examples/graphs/2d/graphsaudio/doc/graphsaudio.webp differ
diff --git a/examples/graphs/2d/graphsaudio/graphsaudio.pyproject b/examples/graphs/2d/graphsaudio/graphsaudio.pyproject
new file mode 100644 (file)
index 0000000..eff7919
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["main.py", "GraphsAudio/Main.qml", "GraphsAudio/qmldir"]
+}
diff --git a/examples/graphs/2d/graphsaudio/main.py b/examples/graphs/2d/graphsaudio/main.py
new file mode 100644 (file)
index 0000000..239aee0
--- /dev/null
@@ -0,0 +1,80 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from PySide6.QtCore import QObject, QPointF, Slot, Signal
+from PySide6.QtMultimedia import QAudioFormat, QAudioSource, QMediaDevices
+from PySide6.QtWidgets import QMessageBox
+from PySide6.QtQml import QQmlApplicationEngine
+from PySide6.QtGui import QGuiApplication
+
+
+SAMPLE_COUNT = 2000
+RESOLUTION = 4
+
+
+class Audio(QObject):
+    dataUpdated = Signal(list)
+
+    def __init__(self, device):
+        super().__init__()
+
+        format_audio = QAudioFormat()
+        format_audio.setSampleRate(8000)
+        format_audio.setChannelCount(1)
+        format_audio.setSampleFormat(QAudioFormat.UInt8)
+
+        self.device_name = device.description()
+
+        self._audio_input = QAudioSource(device, format_audio, self)
+        self._io_device = self._audio_input.start()
+        self._io_device.readyRead.connect(self._readyRead)
+
+        self._buffer = [QPointF(x, 0) for x in range(SAMPLE_COUNT)]
+
+    def closeEvent(self, event):
+        if self._audio_input is not None:
+            self._audio_input.stop()
+        event.accept()
+
+    @Slot()
+    def _readyRead(self):
+        data = self._io_device.readAll()
+        available_samples = data.size() // RESOLUTION
+        start = 0
+        if (available_samples < SAMPLE_COUNT):
+            start = SAMPLE_COUNT - available_samples
+            for s in range(start):
+                self._buffer[s].setY(self._buffer[s + available_samples].y())
+
+        data_index = 0
+        for s in range(start, SAMPLE_COUNT):
+            value = (ord(data[data_index]) - 128) / 128
+            self._buffer[s].setY(value)
+            data_index = data_index + RESOLUTION
+
+        self.dataUpdated.emit(self._buffer)
+
+
+if __name__ == '__main__':
+    app = QGuiApplication(sys.argv)
+    engine = QQmlApplicationEngine()
+
+    input_devices = QMediaDevices.audioInputs()
+    if not input_devices:
+        QMessageBox.warning(None, "audio", "There is no audio input device available.")
+        sys.exit(-1)
+
+    audio_bridge = Audio(input_devices[0])
+    engine.rootContext().setContextProperty("audio_bridge", audio_bridge)
+
+    device = input_devices[0]
+    device_name = device.description()
+    engine.rootContext().setContextProperty("device_name", device_name)
+
+    engine.addImportPath(Path(__file__).parent)
+    engine.loadFromModule("GraphsAudio", "Main")
+
+    sys.exit(app.exec())
index 27dad47054f92c89acbe7145155476f127827da9..58a0d531b9ca82e54634f1d063d1b9fb31ba6f1a 100644 (file)
@@ -26,6 +26,7 @@ class HighlightSeries(QSurface3DSeries):
         self._position = {}
         self._topographicSeries = None
         self._minHeight = 0.0
+        self._height_adjustment = 5.0
         self.setDrawMode(QSurface3DSeries.DrawFlag.DrawSurface)
         self.setShading(QSurface3DSeries.Shading.Flat)
         self.setVisible(False)
@@ -72,7 +73,7 @@ class HighlightSeries(QSurface3DSeries):
             srcRow = srcArray[i]
             for j in range(startX, endX):
                 pos = srcRow.at(j).position()
-                pos.setY(pos.y() + 0.1)
+                pos.setY(pos.y() + self._height_adjustment)
                 item = QSurfaceDataItem(QVector3D(pos))
                 newRow.append(item)
             dataArray.append(newRow)
@@ -93,3 +94,8 @@ class HighlightSeries(QSurface3DSeries):
 
         self.setBaseGradient(gr)
         self.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient)
+
+        self.handle_zoom_change(ratio)
+
+    def handle_zoom_change(self, zoom):
+        self._height_adjustment = (1.2 - zoom) * 10.0
index 946c9626141aa43729de5b92e4dc5c881888e1ce..984bf9df2b81bdc79f5dca607c24d24116047814 100644 (file)
@@ -5,7 +5,7 @@ from __future__ import annotations
 from enum import Enum
 from math import sin, cos, degrees, sqrt
 
-from PySide6.QtCore import QObject, Signal, Slot, Qt
+from PySide6.QtCore import QObject, Signal, Slot, Qt, QRandomGenerator
 from PySide6.QtGui import QVector2D, QVector3D
 from PySide6.QtGraphs import (QAbstract3DSeries,
                               QScatterDataItem, QScatterDataProxy,
@@ -27,7 +27,7 @@ class InputState(Enum):
 
 class ScatterDataModifier(QObject):
 
-    backgroundEnabledChanged = Signal(bool)
+    backgroundVisibleChanged = Signal(bool)
     gridVisibleChanged = Signal(bool)
     shadowQualityChanged = Signal(int)
 
@@ -42,11 +42,11 @@ class ScatterDataModifier(QObject):
         self._itemCount = LOWER_NUMBER_OF_ITEMS
         self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER
 
-        self._graph.activeTheme().setTheme(QGraphsTheme.Theme.MixSeries)
-        self._graph.activeTheme().setColorScheme(QGraphsTheme.ColorScheme.Dark)
         self._graph.setShadowQuality(QtGraphs3D.ShadowQuality.SoftHigh)
         self._graph.setCameraPreset(QtGraphs3D.CameraPreset.Front)
         self._graph.setCameraZoomLevel(80.0)
+        self._graph.activeTheme().setTheme(QGraphsTheme.Theme.MixSeries)
+        self._graph.activeTheme().setColorScheme(QGraphsTheme.ColorScheme.Dark)
 
         self._proxy = QScatterDataProxy()
         self._series = QScatter3DSeries(self._proxy)
@@ -99,7 +99,7 @@ class ScatterDataModifier(QObject):
     def changeTheme(self, theme):
         currentTheme = self._graph.activeTheme()
         currentTheme.setTheme(QGraphsTheme.Theme(theme))
-        self.backgroundEnabledChanged.emit(currentTheme.isPlotAreaBackgroundVisible())
+        self.backgroundVisibleChanged.emit(currentTheme.isPlotAreaBackgroundVisible())
         self.gridVisibleChanged.emit(currentTheme.isGridVisible())
 
     @Slot()
@@ -114,45 +114,6 @@ class ScatterDataModifier(QObject):
     def shadowQualityUpdatedByVisual(self, sq):
         self.shadowQualityChanged.emit(sq.value)
 
-    @Slot(int)
-    def changeShadowQuality(self, quality):
-        sq = QtGraphs3D.ShadowQuality(quality)
-        self._graph.setShadowQuality(sq)
-
-    @Slot(int)
-    def setPlotAreaBackgroundVisible(self, state):
-        enabled = state == Qt.CheckState.Checked
-        self._graph.activeTheme().setPlotAreaBackgroundVisible(enabled)
-
-    @Slot(int)
-    def setGridVisible(self, state):
-        self._graph.activeTheme().setGridVisible(state == Qt.Checked.value)
-
-    @Slot()
-    def toggleItemCount(self):
-        if self._itemCount == NUMBER_OF_ITEMS:
-            self._itemCount = LOWER_NUMBER_OF_ITEMS
-            self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER
-        else:
-            self._itemCount = NUMBER_OF_ITEMS
-            self._CURVE_DIVIDER = CURVE_DIVIDER
-
-        self._graph.seriesList()[0].dataProxy().resetArray([])
-        self.addData()
-
-    @Slot()
-    def toggleRanges(self):
-        if not self._autoAdjust:
-            self._graph.axisX().setAutoAdjustRange(True)
-            self._graph.axisZ().setAutoAdjustRange(True)
-            self._dragSpeedModifier = 1.5
-            self._autoAdjust = True
-        else:
-            self._graph.axisX().setRange(-10.0, 10.0)
-            self._graph.axisZ().setRange(-10.0, 10.0)
-            self._dragSpeedModifier = float(15)
-            self._autoAdjust = False
-
     @Slot(QtGraphs3D.ElementType)
     def handleElementSelected(self, type):
         if type == QtGraphs3D.ElementType.AxisXLabel:
@@ -197,3 +158,69 @@ class ScatterDataModifier(QObject):
             # No need to use adjusted y move here
             distance = move.y() / self._dragSpeedModifier
             axis.setRange(axis.min() + distance, axis.max() + distance)
+
+    @Slot(int)
+    def changeShadowQuality(self, quality):
+        sq = QtGraphs3D.ShadowQuality(quality)
+        self._graph.setShadowQuality(sq)
+
+    @Slot(int)
+    def setBackgroundVisible(self, state):
+        enabled = state == Qt.CheckState.Checked
+        self._graph.activeTheme().setPlotAreaBackgroundVisible(enabled)
+
+    @Slot(int)
+    def setGridVisible(self, state):
+        self._graph.activeTheme().setGridVisible(state == Qt.Checked.value)
+
+    @Slot()
+    def toggleItemCount(self):
+        if self._itemCount == NUMBER_OF_ITEMS:
+            self._itemCount = LOWER_NUMBER_OF_ITEMS
+            self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER
+        else:
+            self._itemCount = NUMBER_OF_ITEMS
+            self._CURVE_DIVIDER = CURVE_DIVIDER
+
+        self._graph.seriesList()[0].dataProxy().resetArray([])
+        self.addData()
+
+    @Slot()
+    def toggleRanges(self):
+        if not self._autoAdjust:
+            self._graph.axisX().setAutoAdjustRange(True)
+            self._graph.axisZ().setAutoAdjustRange(True)
+            self._dragSpeedModifier = 1.5
+            self._autoAdjust = True
+        else:
+            self._graph.axisX().setRange(-10.0, 10.0)
+            self._graph.axisZ().setRange(-10.0, 10.0)
+            self._dragSpeedModifier = float(15)
+            self._autoAdjust = False
+
+    def adjust_minimum_range(self, range):
+        if self._itemCount == LOWER_NUMBER_OF_ITEMS:
+            range *= 1.45
+        else:
+            range *= 4.95
+
+        self._graph.axisX().setMin(range)
+        self._graph.axisZ().setMin(range)
+        self._autoAdjust = False
+
+    def adjust_maximum_range(self, range):
+        if self._itemCount == LOWER_NUMBER_OF_ITEMS:
+            range *= 1.45
+        else:
+            range *= 4.95
+
+        self._graph.axisX().setMax(range)
+        self._graph.axisZ().setMax(range)
+        self._autoAdjust = False
+
+    def rand_vector() -> QVector3D:
+        generator = QRandomGenerator.global_()
+        x = float(generator.bounded(100)) / 2.0 - float(generator.bounded(100)) / 2.0
+        y = float(generator.bounded(100)) / 100.0 - float(generator.bounded(100)) / 100.0
+        z = float(generator.bounded(100)) / 2.0 - float(generator.bounded(100)) / 2.0
+        return QVector3D(x, y, z)
index 1b5c507a93d4c7ceea56cd1e2f285506d04aaf86..050ce2854643735128783e7cd1cd239e37d08707 100644 (file)
@@ -5,7 +5,7 @@ from __future__ import annotations
 from PySide6.QtCore import QObject, QSize, Qt
 from PySide6.QtWidgets import (QCheckBox, QComboBox, QCommandLinkButton,
                                QLabel, QHBoxLayout, QSizePolicy,
-                               QVBoxLayout, QWidget, )
+                               QVBoxLayout, QWidget, QSlider)
 from PySide6.QtQuickWidgets import QQuickWidget
 from PySide6.QtGraphs import QAbstract3DSeries
 from PySide6.QtGraphsWidgets import Q3DScatterWidgetItem
@@ -42,10 +42,15 @@ class ScatterGraph(QObject):
         itemCountButton.setDescription("Switch between 900 and 10000 data points")
         itemCountButton.setIconSize(QSize(0, 0))
 
-        rangeButton = QCommandLinkButton(self._scatterWidget)
-        rangeButton.setText("Toggle axis ranges")
-        rangeButton.setDescription("Switch between automatic axis ranges and preset ranges")
-        rangeButton.setIconSize(QSize(0, 0))
+        range_min_slider = QSlider(Qt.Horizontal, self._scatterWidget)
+        range_min_slider.setMinimum(-10)
+        range_min_slider.setMaximum(1)
+        range_min_slider.setValue(-10)
+
+        range_max_slider = QSlider(Qt.Horizontal, self._scatterWidget)
+        range_max_slider.setMinimum(1)
+        range_max_slider.setMaximum(10)
+        range_max_slider.setValue(10)
 
         backgroundCheckBox = QCheckBox(self._scatterWidget)
         backgroundCheckBox.setText("Show graph background")
@@ -89,7 +94,8 @@ class ScatterGraph(QObject):
 
         vLayout.addWidget(cameraButton)
         vLayout.addWidget(itemCountButton)
-        vLayout.addWidget(rangeButton)
+        vLayout.addWidget(range_min_slider)
+        vLayout.addWidget(range_max_slider)
         vLayout.addWidget(backgroundCheckBox)
         vLayout.addWidget(gridCheckBox)
         vLayout.addWidget(smoothCheckBox)
@@ -104,13 +110,14 @@ class ScatterGraph(QObject):
 
         cameraButton.clicked.connect(modifier.changePresetCamera)
         itemCountButton.clicked.connect(modifier.toggleItemCount)
-        rangeButton.clicked.connect(modifier.toggleRanges)
+        range_min_slider.valueChanged.connect(modifier.adjust_minimum_range)
+        range_max_slider.valueChanged.connect(modifier.adjust_maximum_range)
 
-        backgroundCheckBox.checkStateChanged.connect(modifier.setPlotAreaBackgroundVisible)
+        backgroundCheckBox.checkStateChanged.connect(modifier.setBackgroundVisible)
         gridCheckBox.checkStateChanged.connect(modifier.setGridVisible)
         smoothCheckBox.checkStateChanged.connect(modifier.setSmoothDots)
 
-        modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked)
+        modifier.backgroundVisibleChanged.connect(backgroundCheckBox.setChecked)
         modifier.gridVisibleChanged.connect(gridCheckBox.setChecked)
         itemStyleList.currentIndexChanged.connect(modifier.changeStyle)
 
index 28ce3454cf378bcc498136f7e2590d84f6ff258a..10ed66e3f0db766cbd10c3065d8c77af0ad2e10b 100644 (file)
@@ -5,7 +5,8 @@ from __future__ import annotations
 import sys
 
 from PySide6.QtCore import QPoint, QTimer, QTime, Qt
-from PySide6.QtGui import QGuiApplication, QPainter, QPalette, QPolygon, QRasterWindow
+from PySide6.QtGui import (QGuiApplication, QPainter, QPainterStateGuard,
+                           QPalette, QPolygon, QRasterWindow)
 
 """Simplified PySide6 port of the gui/analogclock example from Qt v6.x"""
 
@@ -54,10 +55,9 @@ class AnalogClockWindow(QRasterWindow):
         painter.setPen(Qt.PenStyle.NoPen)
         painter.setBrush(self._hour_color)
 
-        painter.save()
-        painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0)))
-        painter.drawConvexPolygon(self._hour_hand)
-        painter.restore()
+        with QPainterStateGuard(painter):
+            painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0)))
+            painter.drawConvexPolygon(self._hour_hand)
 
         for _ in range(0, 12):
             painter.drawRect(73, -3, 16, 6)
@@ -65,19 +65,17 @@ class AnalogClockWindow(QRasterWindow):
 
         painter.setBrush(self._minute_color)
 
-        painter.save()
-        painter.rotate(6.0 * time.minute())
-        painter.drawConvexPolygon(self._minute_hand)
-        painter.restore()
+        with QPainterStateGuard(painter):
+            painter.rotate(6.0 * time.minute())
+            painter.drawConvexPolygon(self._minute_hand)
 
         painter.setBrush(self._seconds_color)
 
-        painter.save()
-        painter.rotate(6.0 * time.second())
-        painter.drawConvexPolygon(self._seconds_hand)
-        painter.drawEllipse(-3, -3, 6, 6)
-        painter.drawEllipse(-5, -68, 10, 10)
-        painter.restore()
+        with QPainterStateGuard(painter):
+            painter.rotate(6.0 * time.second())
+            painter.drawConvexPolygon(self._seconds_hand)
+            painter.drawEllipse(-3, -3, 6, 6)
+            painter.drawEllipse(-5, -68, 10, 10)
 
         painter.setPen(self._minute_color)
 
index ecc8f70109bb972bdb075c2994a64f6990efd8f3..90a8fed46ca3ea846b884af78f87e04f280187f9 100644 (file)
@@ -296,7 +296,7 @@ class Camera(QMainWindow):
 
     @Slot(QAction)
     def updateCameraDevice(self, action):
-        self.setCamera(QCameraDevice(action))
+        self.setCamera(QCameraDevice(action.data()))
 
     @Slot()
     def displayViewfinder(self):
index 55c41440976ff52ff98bcb2120b824dc76b457fc..f362498020ef50bf8d5d5ffc3b5809afaea34c60 100644 (file)
@@ -7,13 +7,16 @@ import QtWebEngine
 QtObject {
     id: root
 
-    property QtObject defaultProfile: WebEngineProfile {
+    property QtObject defaultProfilePrototype : WebEngineProfilePrototype {
         storageName: "Profile"
-        offTheRecord: false
+        Component.onCompleted: {
+            let fullVersionList = defaultProfilePrototype.instance().clientHints.fullVersionList;
+            fullVersionList["QuickNanoBrowser"] = "1.0";
+            defaultProfilePrototype.instance().clientHints.fullVersionList = fullVersionList;
+        }
     }
 
-    property QtObject otrProfile: WebEngineProfile {
-        offTheRecord: true
+    property QtObject otrPrototype : WebEngineProfilePrototype {
     }
 
     property Component browserWindowComponent: BrowserWindow {
@@ -34,7 +37,7 @@ QtObject {
         return newDialog;
     }
     function load(url) {
-        var browserWindow = createWindow(defaultProfile);
+        var browserWindow = createWindow(defaultProfilePrototype.instance());
         browserWindow.currentWebView.url = url;
     }
 }
index a517c5a51130d24c8554e5cf3b7c996e44297d44..365d77d217b12e17da8b233c1fdfc1690a85c0a4 100644 (file)
@@ -4,7 +4,8 @@
 import QtCore
 import QtQml
 import QtQuick
-import QtQuick.Controls
+import QtQuick.Controls.Fusion
+import QtQuick.Dialogs
 import QtQuick.Layouts
 import QtQuick.Window
 import QtWebEngine
@@ -44,6 +45,7 @@ ApplicationWindow {
         property alias webRTCPublicInterfacesOnly : webRTCPublicInterfacesOnly.checked
         property alias devToolsEnabled: devToolsEnabled.checked
         property alias pdfViewerEnabled: pdfViewerEnabled.checked
+        property int imageAnimationPolicy: WebEngineSettings.ImageAnimationPolicy.Allow
     }
 
     Action {
@@ -70,7 +72,7 @@ ApplicationWindow {
     Action {
         shortcut: StandardKey.AddTab
         onTriggered: {
-            tabBar.createTab(tabBar.count != 0 ? currentWebView.profile : defaultProfile);
+            tabBar.createTab(tabBar.count != 0 ? currentWebView.profile : defaultProfilePrototype.instance());
             addressBar.forceActiveFocus();
             addressBar.selectAll();
         }
@@ -317,10 +319,10 @@ ApplicationWindow {
                         id: offTheRecordEnabled
                         text: "Off The Record"
                         checkable: true
-                        checked: currentWebView && currentWebView.profile === otrProfile
+                        checked: currentWebView && currentWebView.profile === otrPrototype.instance()
                         onToggled: function(checked) {
                             if (currentWebView) {
-                                currentWebView.profile = checked ? otrProfile : defaultProfile;
+                                currentWebView.profile = checked ? otrPrototype.instance() : defaultProfilePrototype.instance();
                             }
                         }
                     }
@@ -362,10 +364,49 @@ ApplicationWindow {
                     }
                     MenuItem {
                         id: pdfViewerEnabled
-                        text: "PDF viewer enabled"
+                        text: "PDF Viewer Enabled"
                         checkable: true
                         checked: WebEngine.settings.pdfViewerEnabled
                     }
+
+                    Menu {
+                        id: imageAnimationPolicy
+                        title: "Image Animation Policy"
+
+                        MenuItem {
+                            id: disableImageAnimation
+                            text: "Disable All Image Animation"
+                            checkable: true
+                            autoExclusive: true
+                            checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Disallow
+                            onTriggered: {
+                                appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Disallow
+                            }
+                        }
+
+                        MenuItem {
+                            id: allowImageAnimation
+                            text: "Allow All Animated Images"
+                            checkable: true
+                            autoExclusive: true
+                            checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Allow
+                            onTriggered : {
+                                appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Allow
+                            }
+                        }
+
+                        MenuItem {
+                            id: animateImageOnce
+                            text: "Animate Image Once"
+                            checkable: true
+                            autoExclusive: true
+                            checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.AnimateOnce
+                            onTriggered : {
+                                appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.AnimateOnce
+                            }
+                        }
+                    }
+
                 }
             }
         }
@@ -455,7 +496,7 @@ ApplicationWindow {
         anchors.top: parent.top
         anchors.left: parent.left
         anchors.right: parent.right
-        Component.onCompleted: createTab(defaultProfile)
+        Component.onCompleted: createTab(defaultProfilePrototype.instance())
 
         function createTab(profile, focusOnNewTab = true, url = undefined) {
             var webview = tabComponent.createObject(tabLayout, {profile: profile});
@@ -471,7 +512,6 @@ ApplicationWindow {
         }
 
         function removeView(index) {
-            tabBar.removeItem(index);
             if (tabBar.count > 1) {
                 tabBar.removeItem(tabBar.itemAt(index));
                 tabLayout.children[index].destroy();
@@ -521,8 +561,15 @@ ApplicationWindow {
                 settings.touchIconsEnabled: appSettings.touchIconsEnabled
                 settings.webRTCPublicInterfacesOnly: appSettings.webRTCPublicInterfacesOnly
                 settings.pdfViewerEnabled: appSettings.pdfViewerEnabled
+                settings.imageAnimationPolicy: appSettings.imageAnimationPolicy
+                settings.screenCaptureEnabled: true
 
                 onCertificateError: function(error) {
+                    if (!error.isMainFrame) {
+                        error.rejectCertificate();
+                        return;
+                    }
+
                     error.defer();
                     sslDialog.enqueue(error);
                 }
@@ -565,6 +612,11 @@ ApplicationWindow {
                     request.accept();
                 }
 
+                onDesktopMediaRequested: function(request) {
+                    // select the primary screen
+                    request.selectScreen(request.screensModel.index(0, 0));
+                }
+
                 onRenderProcessTerminated: function(terminationStatus, exitCode) {
                     var status = "";
                     switch (terminationStatus) {
@@ -603,10 +655,12 @@ ApplicationWindow {
                         findBar.reset();
                 }
 
-                onFeaturePermissionRequested: function(securityOrigin, feature) {
-                    featurePermissionDialog.securityOrigin = securityOrigin;
-                    featurePermissionDialog.feature = feature;
-                    featurePermissionDialog.visible = true;
+                onPermissionRequested: function(permission) {
+                    permissionDialog.permission = permission;
+                    permissionDialog.visible = true;
+                }
+                onWebAuthUxRequested: function(request) {
+                    webAuthDialog.init(request);
                 }
 
                 Timer {
@@ -688,7 +742,7 @@ ApplicationWindow {
         }
     }
     Dialog {
-        id: featurePermissionDialog
+        id: permissionDialog
         anchors.centerIn: parent
         width: Math.min(browserWindow.width, browserWindow.height) / 3 * 2
         contentWidth: mainTextForPermissionDialog.width
@@ -696,53 +750,59 @@ ApplicationWindow {
         standardButtons: Dialog.No | Dialog.Yes
         title: "Permission Request"
 
-        property var feature;
-        property url securityOrigin;
+        property var permission;
 
         contentItem: Item {
             Label {
                 id: mainTextForPermissionDialog
-                text: featurePermissionDialog.questionForFeature()
             }
         }
 
-        onAccepted: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, true)
-        onRejected: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, false)
+        onAccepted: permission.grant()
+        onRejected: permission.deny()
         onVisibleChanged: {
-            if (visible)
+            if (visible) {
+                mainTextForPermissionDialog.text = questionForPermissionType();
                 width = contentWidth + 20;
+            }
         }
 
-        function questionForFeature() {
-            var question = "Allow " + securityOrigin + " to "
+        function questionForPermissionType() {
+            var question = "Allow " + permission.origin + " to "
 
-            switch (feature) {
-            case WebEngineView.Geolocation:
+            switch (permission.permissionType) {
+            case WebEnginePermission.PermissionType.Geolocation:
                 question += "access your location information?";
                 break;
-            case WebEngineView.MediaAudioCapture:
+            case WebEnginePermission.PermissionType.MediaAudioCapture:
                 question += "access your microphone?";
                 break;
-            case WebEngineView.MediaVideoCapture:
+            case WebEnginePermission.PermissionType.MediaVideoCapture:
                 question += "access your webcam?";
                 break;
-            case WebEngineView.MediaVideoCapture:
+            case WebEnginePermission.PermissionType.MediaAudioVideoCapture:
                 question += "access your microphone and webcam?";
                 break;
-            case WebEngineView.MouseLock:
+            case WebEnginePermission.PermissionType.MouseLock:
                 question += "lock your mouse cursor?";
                 break;
-            case WebEngineView.DesktopVideoCapture:
+            case WebEnginePermission.PermissionType.DesktopVideoCapture:
                 question += "capture video of your desktop?";
                 break;
-            case WebEngineView.DesktopAudioVideoCapture:
+            case WebEnginePermission.PermissionType.DesktopAudioVideoCapture:
                 question += "capture audio and video of your desktop?";
                 break;
-            case WebEngineView.Notifications:
+            case WebEnginePermission.PermissionType.Notifications:
                 question += "show notification on your desktop?";
                 break;
+            case WebEnginePermission.PermissionType.ClipboardReadWrite:
+                question += "read from and write to your clipboard?";
+                break;
+            case WebEnginePermission.PermissionType.LocalFontsAccess:
+                question += "access the fonts stored on your machine?";
+                break;
             default:
-                question += "access unknown or unsupported feature [" + feature + "] ?";
+                question += "access unknown or unsupported permission type [" + permission.permissionType + "] ?";
                 break;
             }
 
@@ -760,10 +820,34 @@ ApplicationWindow {
         anchors.fill: parent
     }
 
+    WebAuthDialog {
+        id: webAuthDialog
+        visible: false
+    }
+
+    MessageDialog {
+        id: downloadAcceptDialog
+        property var downloadRequest: downloadView.pendingDownloadRequest
+        title: "Download requested"
+        text: downloadRequest ? downloadRequest.suggestedFileName : ""
+        buttons: Dialog.No | Dialog.Yes
+        onAccepted: {
+            downloadView.visible = true;
+            downloadView.append(downloadRequest);
+            downloadRequest.accept();
+        }
+        onRejected: {
+            downloadRequest.cancel();
+        }
+        onButtonClicked: {
+            visible = false;
+        }
+        visible: false
+    }
+
     function onDownloadRequested(download) {
-        downloadView.visible = true;
-        downloadView.append(download);
-        download.accept();
+        downloadView.pendingDownloadRequest = download;
+        downloadAcceptDialog.visible = true;
     }
 
     FindBar {
index e16647cdba3caf06e359f433bf9d7841b949dae8..b116ab86734e12fb6d48ce64dc4eb504845211d5 100644 (file)
@@ -2,13 +2,14 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
 import QtQuick
-import QtQuick.Controls
+import QtQuick.Controls.Fusion
 import QtWebEngine
 import QtQuick.Layouts
 
 Rectangle {
     id: downloadView
     color: "lightgray"
+    property var pendingDownloadRequest: null
 
     ListModel {
         id: downloadModel
index 4d130a22b25fc711e3fec53996cd6b9c79f6182f..409d8dcffe8ef2c830273c4ab5db10cbbba51a3e 100644 (file)
@@ -2,7 +2,7 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
 import QtQuick
-import QtQuick.Controls
+import QtQuick.Controls.Fusion
 import QtQuick.Layouts
 
 Rectangle {
@@ -51,6 +51,7 @@ Rectangle {
             TextField {
                 id: findTextField
                 anchors.fill: parent
+                color: "black"
                 background: Rectangle {
                     color: "transparent"
                 }
@@ -64,6 +65,7 @@ Rectangle {
         Label {
             text: activeMatch + "/" + numberOfMatches
             visible: findTextField.text != ""
+            color: "black"
         }
 
         Rectangle {
@@ -79,17 +81,29 @@ Rectangle {
             text: "<"
             enabled: numberOfMatches > 0
             onClicked: root.findPrevious()
+            contentItem: Text {
+                color: "black"
+                text: parent.text
+            }
         }
 
         ToolButton {
             text: ">"
             enabled: numberOfMatches > 0
             onClicked: root.findNext()
+            contentItem: Text {
+                color: "black"
+                text: parent.text
+            }
         }
 
         ToolButton {
             text: "x"
             onClicked: root.visible = false
+            contentItem: Text {
+                color: "black"
+                text: parent.text
+            }
         }
     }
 }
index e5c667c5140234efe30ae7cec9be82843bada8f8..f68cc2e378274b1303a08196b607b135c5a855e8 100644 (file)
@@ -54,7 +54,7 @@ if __name__ == '__main__':
                                  nargs='?', type=str)
     options = argument_parser.parse_args()
 
-    url = url_from_user_input(options.url) if options.url else QUrl("https://www.qt.io")
+    url = url_from_user_input(options.url) if options.url else QUrl("chrome://qt")
 
     app_args = sys.argv
     if options.single_process:
index 1bf458e79ff4e7beaaf6398b8d949d4a31ac6318..29e4520850510b7b10fb5d3bddcc9c788d8b6bde 100644 (file)
@@ -22,9 +22,6 @@ class Browser(QObject):
         # remaining window
         self._download_manager_widget.setAttribute(Qt.WidgetAttribute.WA_QuitOnClose, False)
 
-        dp = QWebEngineProfile.defaultProfile()
-        dp.downloadRequested.connect(self._download_manager_widget.download_requested)
-
     def create_hidden_window(self, offTheRecord=False):
         if not offTheRecord and not self._profile:
             name = "simplebrowser." + qWebEngineChromiumVersion()
@@ -34,11 +31,15 @@ class Browser(QObject):
             s.setAttribute(QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True)
             s.setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True)
             s.setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, False)
+            s.setAttribute(QWebEngineSettings.ScreenCaptureEnabled, True)
             self._profile.downloadRequested.connect(
                 self._download_manager_widget.download_requested)
 
         profile = QWebEngineProfile.defaultProfile() if offTheRecord else self._profile
         main_window = BrowserWindow(self, profile, False)
+        profile.setPersistentPermissionsPolicy(
+            QWebEngineProfile.PersistentPermissionsPolicy.AskEveryTime)
+
         self._windows.append(main_window)
         main_window.about_to_close.connect(self._remove_window)
         return main_window
index 2602c5db92e2b7453f58e3e7fd36a899afb0a608..3d42974a7666cf17fa4cbed72d701d98b477771f 100644 (file)
@@ -16,6 +16,7 @@ from browser import Browser
 
 import data.rc_simplebrowser  # noqa: F401
 
+
 if __name__ == "__main__":
     parser = ArgumentParser(description="Qt Widgets Web Browser",
                             formatter_class=RawTextHelpFormatter)
@@ -34,13 +35,15 @@ if __name__ == "__main__":
     QLoggingCategory.setFilterRules("qt.webenginecontext.debug=true")
 
     s = QWebEngineProfile.defaultProfile().settings()
-    s.setAttribute(QWebEngineSettings.WebAttribute.PluginsEnabled, True)
-    s.setAttribute(QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True)
+    s.setAttribute(QWebEngineSettings.PluginsEnabled, True)
+    s.setAttribute(QWebEngineSettings.DnsPrefetchEnabled, True)
+    s.setAttribute(QWebEngineSettings.ScreenCaptureEnabled, True)
 
     browser = Browser()
     window = browser.create_hidden_window()
 
-    url = QUrl.fromUserInput(args.url) if args.url else QUrl("https://www.qt.io")
+    url = QUrl.fromUserInput(args.url) if args.url else QUrl("chrome://qt")
     window.tab_widget().set_url(url)
     window.show()
+
     sys.exit(app.exec())
diff --git a/examples/webenginewidgets/simplebrowser/ui_webauthdialog.py b/examples/webenginewidgets/simplebrowser/ui_webauthdialog.py
new file mode 100644 (file)
index 0000000..eb54ba6
--- /dev/null
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'webauthdialog.ui'
+##
+## Created by: Qt User Interface Compiler version 6.8.1
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+    QMetaObject, QObject, QPoint, QRect,
+    QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+    QFont, QFontDatabase, QGradient, QIcon,
+    QImage, QKeySequence, QLinearGradient, QPainter,
+    QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
+    QGroupBox, QLabel, QLayout, QLineEdit,
+    QSizePolicy, QVBoxLayout, QWidget)
+
+class Ui_WebAuthDialog(object):
+    def setupUi(self, WebAuthDialog):
+        if not WebAuthDialog.objectName():
+            WebAuthDialog.setObjectName(u"WebAuthDialog")
+        WebAuthDialog.resize(563, 397)
+        self.buttonBox = QDialogButtonBox(WebAuthDialog)
+        self.buttonBox.setObjectName(u"buttonBox")
+        self.buttonBox.setGeometry(QRect(20, 320, 471, 32))
+        self.buttonBox.setOrientation(Qt.Horizontal)
+        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok|QDialogButtonBox.Retry)
+        self.m_headingLabel = QLabel(WebAuthDialog)
+        self.m_headingLabel.setObjectName(u"m_headingLabel")
+        self.m_headingLabel.setGeometry(QRect(30, 20, 321, 16))
+        self.m_headingLabel.setWordWrap(False)
+        self.m_description = QLabel(WebAuthDialog)
+        self.m_description.setObjectName(u"m_description")
+        self.m_description.setGeometry(QRect(30, 60, 491, 31))
+        self.m_description.setWordWrap(False)
+        self.layoutWidget = QWidget(WebAuthDialog)
+        self.layoutWidget.setObjectName(u"layoutWidget")
+        self.layoutWidget.setGeometry(QRect(20, 100, 471, 171))
+        self.m_mainVerticalLayout = QVBoxLayout(self.layoutWidget)
+        self.m_mainVerticalLayout.setObjectName(u"m_mainVerticalLayout")
+        self.m_mainVerticalLayout.setSizeConstraint(QLayout.SetDefaultConstraint)
+        self.m_mainVerticalLayout.setContentsMargins(0, 0, 0, 0)
+        self.m_pinGroupBox = QGroupBox(self.layoutWidget)
+        self.m_pinGroupBox.setObjectName(u"m_pinGroupBox")
+        self.m_pinGroupBox.setFlat(True)
+        self.m_pinLabel = QLabel(self.m_pinGroupBox)
+        self.m_pinLabel.setObjectName(u"m_pinLabel")
+        self.m_pinLabel.setGeometry(QRect(10, 20, 58, 16))
+        self.m_pinLineEdit = QLineEdit(self.m_pinGroupBox)
+        self.m_pinLineEdit.setObjectName(u"m_pinLineEdit")
+        self.m_pinLineEdit.setGeometry(QRect(90, 20, 113, 21))
+        self.m_confirmPinLabel = QLabel(self.m_pinGroupBox)
+        self.m_confirmPinLabel.setObjectName(u"m_confirmPinLabel")
+        self.m_confirmPinLabel.setGeometry(QRect(10, 50, 81, 16))
+        self.m_confirmPinLineEdit = QLineEdit(self.m_pinGroupBox)
+        self.m_confirmPinLineEdit.setObjectName(u"m_confirmPinLineEdit")
+        self.m_confirmPinLineEdit.setGeometry(QRect(90, 50, 113, 21))
+        self.m_pinEntryErrorLabel = QLabel(self.m_pinGroupBox)
+        self.m_pinEntryErrorLabel.setObjectName(u"m_pinEntryErrorLabel")
+        self.m_pinEntryErrorLabel.setGeometry(QRect(10, 80, 441, 16))
+
+        self.m_mainVerticalLayout.addWidget(self.m_pinGroupBox)
+
+
+        self.retranslateUi(WebAuthDialog)
+
+        QMetaObject.connectSlotsByName(WebAuthDialog)
+    # setupUi
+
+    def retranslateUi(self, WebAuthDialog):
+        WebAuthDialog.setWindowTitle(QCoreApplication.translate("WebAuthDialog", u"Dialog", None))
+        self.m_headingLabel.setText(QCoreApplication.translate("WebAuthDialog", u"Heading", None))
+        self.m_description.setText(QCoreApplication.translate("WebAuthDialog", u"Description", None))
+        self.m_pinGroupBox.setTitle("")
+        self.m_pinLabel.setText(QCoreApplication.translate("WebAuthDialog", u"PIN", None))
+        self.m_confirmPinLabel.setText(QCoreApplication.translate("WebAuthDialog", u"Confirm PIN", None))
+        self.m_pinEntryErrorLabel.setText(QCoreApplication.translate("WebAuthDialog", u"TextLabel", None))
+    # retranslateUi
+
diff --git a/examples/webenginewidgets/simplebrowser/webauthdialog.py b/examples/webenginewidgets/simplebrowser/webauthdialog.py
new file mode 100644 (file)
index 0000000..162c595
--- /dev/null
@@ -0,0 +1,243 @@
+# Converted from webauthdialog.cpp
+
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from ui_webauthdialog import Ui_WebAuthDialog
+
+from PySide6.QtWidgets import (QDialog, QVBoxLayout, QButtonGroup,
+                               QScrollArea, QWidget, QDialogButtonBox,
+                               QSizePolicy, QRadioButton)
+from PySide6.QtCore import Qt
+from PySide6.QtWebEngineCore import QWebEngineWebAuthUxRequest
+
+
+class WebAuthDialog(QDialog):
+
+    def __init__(self, request, parent=None):
+        super().__init__(parent)
+
+        self.uxRequest = request
+        self.uiWebAuthDialog = Ui_WebAuthDialog()
+        self.uiWebAuthDialog.setupUi(self)
+
+        self.button_group = QButtonGroup(self)
+        self.button_group.setExclusive(True)
+
+        self.scroll_area = QScrollArea(self)
+        self.select_account_widget = QWidget(self)
+        self.scroll_area.setWidget(self.select_account_widget)
+        self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+        self.select_account_widget.resize(400, 150)
+
+        self.select_account_layout = QVBoxLayout(self.select_account_widget)
+        self.uiWebAuthDialog.m_mainVerticalLayout.addWidget(self.scroll_area)
+        self.select_account_layout.setAlignment(Qt.AlignTop)
+
+        self.update_display()
+
+        self.uiWebAuthDialog.buttonBox.rejected.connect(self.onCancelRequest)
+        self.uiWebAuthDialog.buttonBox.accepted.connect(self.onAcceptRequest)
+
+        button = self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry)
+        button.clicked.connect(self.onRetry)
+        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
+
+    def __del__(self):
+        for button in self.button_group.buttons():
+            button.deleteLater()
+
+        if self.button_group:
+            self.button_group.deleteLater()
+            self.button_group = None
+
+        if self.uiWebAuthDialog:
+            del self.uiWebAuthDialog
+            self.uiWebAuthDialog = None
+
+        if self.scroll_area:
+            self.scroll_area.deleteLater()
+            self.scroll_area = None
+
+    def update_display(self):
+        state = self.uxRequest.state()
+        if state == QWebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
+            self.setupSelectAccountUI()
+        elif state == QWebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
+            self.setupCollectPinUI()
+        elif state == QWebEngineWebAuthUxRequest.WebAuthUxState.FinishTokenCollection:
+            self.setupFinishCollectTokenUI()
+        elif state == QWebEngineWebAuthUxRequest.WebAuthUxState.RequestFailed:
+            self.setupErrorUI()
+
+        self.adjustSize()
+
+    def setupSelectAccountUI(self):
+        self.uiWebAuthDialog.m_headingLabel.setText(self.tr("Choose a Passkey"))
+        self.uiWebAuthDialog.m_description.setText(self.tr("Which passkey do you want to use for ")
+                                                   + self.uxRequest.relyingPartyId()
+                                                   + self.tr("? "))
+        self.uiWebAuthDialog.m_pinGroupBox.setVisible(False)
+        self.uiWebAuthDialog.m_mainVerticalLayout.removeWidget(self.uiWebAuthDialog.m_pinGroupBox)
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setVisible(False)
+
+        self.clearSelectAccountButtons()
+        self.scroll_area.setVisible(True)
+        self.select_account_widget.resize(self.width(), self.height())
+        userNames = self.uxRequest.userNames()
+        # Create radio buttons for each name
+        for name in userNames:
+            radioButton = QRadioButton(name)
+            self.select_account_layout.addWidget(radioButton)
+            self.button_group.addButton(radioButton)
+
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok"))
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setVisible(True)
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Cancel).setVisible(True)
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setVisible(False)
+
+    def setupFinishCollectTokenUI(self):
+
+        self.clearSelectAccountButtons()
+        self.uiWebAuthDialog.m_headingLabel.setText(self.tr("Use your security key with")
+                                                    + self.uxRequest.relyingPartyId())
+        self.uiWebAuthDialog.m_description.setText(
+            self.tr("Touch your security key again to complete the request."))
+        self.uiWebAuthDialog.m_pinGroupBox.setVisible(False)
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setVisible(False)
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setVisible(False)
+        self.scroll_area.setVisible(False)
+
+    def setupCollectPinUI(self):
+
+        self.clearSelectAccountButtons()
+        self.uiWebAuthDialog.m_mainVerticalLayout.addWidget(self.uiWebAuthDialog.m_pinGroupBox)
+        self.uiWebAuthDialog.m_pinGroupBox.setVisible(True)
+        self.uiWebAuthDialog.m_confirmPinLabel.setVisible(False)
+        self.uiWebAuthDialog.m_confirmPinLineEdit.setVisible(False)
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Next"))
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setVisible(True)
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Cancel).setVisible(True)
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setVisible(False)
+        self.scroll_area.setVisible(False)
+
+        pinRequestInfo = self.uxRequest.pinRequest()
+
+        if pinRequestInfo.reason == QWebEngineWebAuthUxRequest.PinEntryReason.Challenge:
+            self.uiWebAuthDialog.m_headingLabel.setText(self.tr("PIN Required"))
+            self.uiWebAuthDialog.m_description.setText(
+                self.tr("Enter the PIN for your security key"))
+            self.uiWebAuthDialog.m_confirmPinLabel.setVisible(False)
+            self.uiWebAuthDialog.m_confirmPinLineEdit.setVisible(False)
+        else:
+            if pinRequestInfo.reason == QWebEngineWebAuthUxRequest.PinEntryReason.Set:
+                self.uiWebAuthDialog.m_headingLabel.setText(self.tr("New PIN Required"))
+                self.uiWebAuthDialog.m_description.setText(
+                    self.tr("Set new PIN for your security key"))
+            else:
+                self.uiWebAuthDialog.m_headingLabel.setText(self.tr("Change PIN Required"))
+                self.uiWebAuthDialog.m_description.setText(
+                    self.tr("Change PIN for your security key"))
+
+            self.uiWebAuthDialog.m_confirmPinLabel.setVisible(True)
+            self.uiWebAuthDialog.m_confirmPinLineEdit.setVisible(True)
+
+        errorDetails = ""
+
+        if pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.InternalUvLocked:
+            errorDetails = self.tr("Internal User Verification Locked ")
+        elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.WrongPin:
+            errorDetails = self.tr("Wrong PIN")
+        elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.TooShort:
+            errorDetails = self.tr("Too Short")
+        elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.InvalidCharacters:
+            errorDetails = self.tr("Invalid Characters")
+        elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.SameAsCurrentPin:
+            errorDetails = self.tr("Same as current PIN")
+
+        if errorDetails:
+            errorDetails += f" {pinRequestInfo.remainingAttempts} attempts remaining"
+
+        self.uiWebAuthDialog.m_pinEntryErrorLabel.setText(errorDetails)
+
+    def onCancelRequest(self):
+
+        self.uxRequest.cancel()
+
+    def onAcceptRequest(self):
+
+        state = self.uxRequest.state()
+        if state == QWebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
+            if self.button_group.checkedButton():
+                self.uxRequest.setSelectedAccount(self.button_group.checkedButton().text())
+        elif state == QWebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
+            self.uxRequest.setPin(self.uiWebAuthDialog.m_pinLineEdit.text())
+
+    def setupErrorUI(self):
+
+        self.clearSelectAccountButtons()
+        error_description = ""
+        error_heading = self.tr("Something went wrong")
+        isVisibleRetry = False
+
+        state = self.uxRequest.requestFailureReason()
+        failure_reason = QWebEngineWebAuthUxRequest.RequestFailureReason
+
+        if state == failure_reason.Timeout:
+            error_description = self.tr("Request Timeout")
+        elif state == failure_reason.KeyNotRegistered:
+            error_description = self.tr("Key not registered")
+        elif state == failure_reason.KeyAlreadyRegistered:
+            error_description = self.tr("You already registered self device."
+                                        "Try again with device")
+            isVisibleRetry = True
+        elif state == failure_reason.SoftPinBlock:
+            error_description = self.tr(
+                "The security key is locked because the wrong PIN was entered too many times."
+                "To unlock it, remove and reinsert it.")
+            isVisibleRetry = True
+        elif state == failure_reason.HardPinBlock:
+            error_description = self.tr(
+                "The security key is locked because the wrong PIN was entered too many times."
+                " Yo'll need to reset the security key.")
+        elif state == failure_reason.AuthenticatorRemovedDuringPinEntry:
+            error_description = self.tr(
+                "Authenticator removed during verification. Please reinsert and try again")
+        elif state == failure_reason.AuthenticatorMissingResidentKeys:
+            error_description = self.tr("Authenticator doesn't have resident key support")
+        elif state == failure_reason.AuthenticatorMissingUserVerification:
+            error_description = self.tr("Authenticator missing user verification")
+        elif state == failure_reason.AuthenticatorMissingLargeBlob:
+            error_description = self.tr("Authenticator missing Large Blob support")
+        elif state == failure_reason.NoCommonAlgorithms:
+            error_description = self.tr("Authenticator missing Large Blob support")
+        elif state == failure_reason.StorageFull:
+            error_description = self.tr("Storage Full")
+        elif state == failure_reason.UserConsentDenied:
+            error_description = self.tr("User consent denied")
+        elif state == failure_reason.WinUserCancelled:
+            error_description = self.tr("User Cancelled Request")
+
+        self.uiWebAuthDialog.m_headingLabel.setText(error_heading)
+        self.uiWebAuthDialog.m_description.setText(error_description)
+        self.uiWebAuthDialog.m_description.adjustSize()
+        self.uiWebAuthDialog.m_pinGroupBox.setVisible(False)
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setVisible(False)
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setVisible(isVisibleRetry)
+        if isVisibleRetry:
+            self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setFocus()
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Cancel).setVisible(True)
+        self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Close"))
+        self.scroll_area.setVisible(False)
+
+    def onRetry(self):
+        self.uxRequest.retry()
+
+    def clearSelectAccountButtons(self):
+        buttons = self.button_group.buttons()
+
+        for radio_button in buttons:
+            self.select_account_layout.removeWidget(radio_button)
+            self.button_group.removeButton(radio_button)
+            radio_button.deleteLater()
diff --git a/examples/webenginewidgets/simplebrowser/webauthdialog.ui b/examples/webenginewidgets/simplebrowser/webauthdialog.ui
new file mode 100644 (file)
index 0000000..c8a0456
--- /dev/null
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>WebAuthDialog</class>
+ <widget class="QDialog" name="WebAuthDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>563</width>
+    <height>397</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <widget class="QDialogButtonBox" name="buttonBox">
+   <property name="geometry">
+    <rect>
+     <x>20</x>
+     <y>320</y>
+     <width>471</width>
+     <height>32</height>
+    </rect>
+   </property>
+   <property name="orientation">
+    <enum>Qt::Horizontal</enum>
+   </property>
+   <property name="standardButtons">
+    <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Retry</set>
+   </property>
+  </widget>
+  <widget class="QLabel" name="m_headingLabel">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>20</y>
+     <width>321</width>
+     <height>16</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Heading</string>
+   </property>
+   <property name="wordWrap">
+    <bool>false</bool>
+   </property>
+  </widget>
+  <widget class="QLabel" name="m_description">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>60</y>
+     <width>491</width>
+     <height>31</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Description</string>
+   </property>
+   <property name="wordWrap">
+    <bool>false</bool>
+   </property>
+  </widget>
+  <widget class="QWidget" name="layoutWidget">
+   <property name="geometry">
+    <rect>
+     <x>20</x>
+     <y>100</y>
+     <width>471</width>
+     <height>171</height>
+    </rect>
+   </property>
+   <layout class="QVBoxLayout" name="m_mainVerticalLayout">
+    <property name="sizeConstraint">
+     <enum>QLayout::SetDefaultConstraint</enum>
+    </property>
+    <item>
+     <widget class="QGroupBox" name="m_pinGroupBox">
+      <property name="title">
+       <string/>
+      </property>
+      <property name="flat">
+       <bool>true</bool>
+      </property>
+      <widget class="QLabel" name="m_pinLabel">
+       <property name="geometry">
+        <rect>
+         <x>10</x>
+         <y>20</y>
+         <width>58</width>
+         <height>16</height>
+        </rect>
+       </property>
+       <property name="text">
+        <string>PIN</string>
+       </property>
+      </widget>
+      <widget class="QLineEdit" name="m_pinLineEdit">
+       <property name="geometry">
+        <rect>
+         <x>90</x>
+         <y>20</y>
+         <width>113</width>
+         <height>21</height>
+        </rect>
+       </property>
+      </widget>
+      <widget class="QLabel" name="m_confirmPinLabel">
+       <property name="geometry">
+        <rect>
+         <x>10</x>
+         <y>50</y>
+         <width>81</width>
+         <height>16</height>
+        </rect>
+       </property>
+       <property name="text">
+        <string>Confirm PIN</string>
+       </property>
+      </widget>
+      <widget class="QLineEdit" name="m_confirmPinLineEdit">
+       <property name="geometry">
+        <rect>
+         <x>90</x>
+         <y>50</y>
+         <width>113</width>
+         <height>21</height>
+        </rect>
+       </property>
+      </widget>
+      <widget class="QLabel" name="m_pinEntryErrorLabel">
+       <property name="geometry">
+        <rect>
+         <x>10</x>
+         <y>80</y>
+         <width>441</width>
+         <height>16</height>
+        </rect>
+       </property>
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
index fd82ed9f25c507e2bb0f1f8a81eed926ed6ef8db..5b5ed1733835a6757a5199ea9ebc4037160d981e 100644 (file)
@@ -13,7 +13,6 @@ class WebPopupWindow(QWidget):
 
     def __init__(self, view, profile, parent=None):
         super().__init__(parent, Qt.Window)
-        self.m_urlLineEdit = QLineEdit(self)
         self._url_line_edit = QLineEdit()
         self._fav_action = QAction(self)
         self._view = view
index c7e577176b76900eeeccc684ff3954225b69f375..4b106acb26ee577d7bdbd96c90987332fab6e115 100644 (file)
@@ -5,21 +5,24 @@ from __future__ import annotations
 from functools import partial
 
 from PySide6.QtWebEngineCore import (QWebEngineFileSystemAccessRequest,
-                                     QWebEnginePage)
+                                     QWebEnginePage,
+                                     QWebEngineWebAuthUxRequest)
 from PySide6.QtWebEngineWidgets import QWebEngineView
 
 from PySide6.QtWidgets import QDialog, QMessageBox, QStyle
 from PySide6.QtGui import QIcon
 from PySide6.QtNetwork import QAuthenticator
-from PySide6.QtCore import QTimer, Signal, Slot
+from PySide6.QtCore import QTimer, Signal, Slot, Qt
 
 from webpage import WebPage
 from webpopupwindow import WebPopupWindow
 from ui_passworddialog import Ui_PasswordDialog
 from ui_certificateerrordialog import Ui_CertificateErrorDialog
+from webauthdialog import WebAuthDialog
 
 
 def question_for_feature(feature):
+
     if feature == QWebEnginePage.Geolocation:
         return "Allow %1 to access your location information?"
     if feature == QWebEnginePage.MediaAudioCapture:
@@ -59,6 +62,7 @@ class WebView(QWebEngineView):
         self._loading_icon = QIcon.fromTheme(QIcon.ThemeIcon.ViewRefresh,
                                              QIcon(":view-refresh.png"))
         self._default_icon = QIcon(":text-html.png")
+        self.auth_dialog = None
 
     @Slot()
     def _load_started(self):
@@ -105,6 +109,7 @@ class WebView(QWebEngineView):
                 self.handle_proxy_authentication_required)
             old_page.registerProtocolHandlerRequested.disconnect(
                 self.handle_register_protocol_handler_requested)
+            old_page.webAuthUxRequested.disconnect(self.handle_web_auth_ux_requested)
             old_page.fileSystemAccessRequested.disconnect(self.handle_file_system_access_requested)
 
         self.create_web_action_trigger(page, QWebEnginePage.WebAction.Forward)
@@ -118,6 +123,7 @@ class WebView(QWebEngineView):
         page.proxyAuthenticationRequired.connect(self.handle_proxy_authentication_required)
         page.registerProtocolHandlerRequested.connect(
             self.handle_register_protocol_handler_requested)
+        page.webAuthUxRequested.connect(self.handle_web_auth_ux_requested)
         page.fileSystemAccessRequested.connect(self.handle_file_system_access_requested)
 
     def load_progress(self):
@@ -265,6 +271,28 @@ class WebView(QWebEngineView):
             # Set authenticator null if dialog is cancelled
             auth = QAuthenticator()
 
+    def handle_web_auth_ux_requested(self, request):
+        if self.auth_dialog:
+            self.auth_dialog.deleteLater()
+
+        self.auth_dialog = WebAuthDialog(request, self.window())
+        self.auth_dialog.setModal(False)
+        self.auth_dialog.setWindowFlags(self.auth_dialog.windowFlags()
+                                        & ~Qt.WindowContextHelpButtonHint)
+
+        request.stateChanged.connect(self.on_state_changed)
+        self.auth_dialog.show()
+
+    def on_state_changed(self, state):
+        if state in (QWebEngineWebAuthUxRequest.WebAuthUxState.Completed,
+                     QWebEngineWebAuthUxRequest.WebAuthUxState.Cancelled):
+            if self.auth_dialog:
+                self.auth_dialog.deleteLater()
+                self.auth_dialog = None
+        else:
+            if self.auth_dialog:
+                self.auth_dialog.update_display()
+
     def handle_register_protocol_handler_requested(self, request):
         host = request.origin().host()
         m = f"Allow {host} to open all {request.scheme()} links?"
index 694bb43eb28652d0d161229344e70e582785ccec..96c08fca7a6c7c20a63f8b846a7d18b71cc98ced 100644 (file)
@@ -6,7 +6,7 @@ from __future__ import annotations
 
 from math import (cos, sin, pi)
 
-from PySide6.QtGui import (QPainter, QPolygonF)
+from PySide6.QtGui import (QPainter, QPainterStateGuard, QPolygonF)
 from PySide6.QtCore import (QPointF, QSize, Qt)
 
 PAINTING_SCALE_FACTOR = 20
@@ -39,25 +39,22 @@ class StarRating:
 
     def paint(self, painter, rect, palette, isEditable=False):
         """ Paint the stars (and/or diamonds if we're in editing mode). """
-        painter.save()
-
-        painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
-        painter.setPen(Qt.PenStyle.NoPen)
-
-        if isEditable:
-            painter.setBrush(palette.highlight())
-        else:
-            painter.setBrush(palette.windowText())
-
-        y_offset = (rect.height() - PAINTING_SCALE_FACTOR) / 2
-        painter.translate(rect.x(), rect.y() + y_offset)
-        painter.scale(PAINTING_SCALE_FACTOR, PAINTING_SCALE_FACTOR)
-
-        for i in range(self.MAX_STAR_COUNT):
-            if i < self.star_count:
-                painter.drawPolygon(self._star_polygon, Qt.FillRule.WindingFill)
-            elif isEditable:
-                painter.drawPolygon(self._diamond_polygon, Qt.WindingFill)
-            painter.translate(1.0, 0.0)
-
-        painter.restore()
+        with QPainterStateGuard(painter):
+            painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
+            painter.setPen(Qt.NoPen)
+
+            if isEditable:
+                painter.setBrush(palette.highlight())
+            else:
+                painter.setBrush(palette.windowText())
+
+            y_offset = (rect.height() - PAINTING_SCALE_FACTOR) / 2
+            painter.translate(rect.x(), rect.y() + y_offset)
+            painter.scale(PAINTING_SCALE_FACTOR, PAINTING_SCALE_FACTOR)
+
+            for i in range(self.MAX_STAR_COUNT):
+                if i < self.star_count:
+                    painter.drawPolygon(self._star_polygon, Qt.FillRule.WindingFill)
+                elif isEditable:
+                    painter.drawPolygon(self._diamond_polygon, Qt.FillRule.WindingFill)
+                painter.translate(1.0, 0.0)
index ef4af5d855c096865ba9c515f96a83dcf919fd9b..a712c58039f22cfbb4b370602b503fa88fb2a0c3 100644 (file)
@@ -7,8 +7,8 @@ from __future__ import annotations
 
 from PySide6.QtCore import QPoint, QRect, QSize, Qt, qVersion
 from PySide6.QtGui import (QBrush, QConicalGradient, QLinearGradient, QPainter,
-                           QPainterPath, QPalette, QPen, QPixmap, QPolygon,
-                           QRadialGradient)
+                           QPainterStateGuard, QPainterPath, QPalette, QPen,
+                           QPixmap, QPolygon, QRadialGradient)
 from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QGridLayout,
                                QLabel, QSpinBox, QWidget)
 
@@ -86,44 +86,42 @@ class RenderArea(QWidget):
 
             for x in range(0, self.width(), 100):
                 for y in range(0, self.height(), 100):
-                    painter.save()
-                    painter.translate(x, y)
-                    if self.transformed:
-                        painter.translate(50, 50)
-                        painter.rotate(60.0)
-                        painter.scale(0.6, 0.9)
-                        painter.translate(-50, -50)
-
-                    if self.shape == RenderArea.Line:
-                        painter.drawLine(rect.bottomLeft(), rect.topRight())
-                    elif self.shape == RenderArea.Points:
-                        painter.drawPoints(RenderArea.points)
-                    elif self.shape == RenderArea.Polyline:
-                        painter.drawPolyline(RenderArea.points)
-                    elif self.shape == RenderArea.Polygon:
-                        painter.drawPolygon(RenderArea.points)
-                    elif self.shape == RenderArea.Rect:
-                        painter.drawRect(rect)
-                    elif self.shape == RenderArea.RoundedRect:
-                        painter.drawRoundedRect(rect, 25, 25, Qt.SizeMode.RelativeSize)
-                    elif self.shape == RenderArea.Ellipse:
-                        painter.drawEllipse(rect)
-                    elif self.shape == RenderArea.Arc:
-                        painter.drawArc(rect, start_angle, arc_length)
-                    elif self.shape == RenderArea.Chord:
-                        painter.drawChord(rect, start_angle, arc_length)
-                    elif self.shape == RenderArea.Pie:
-                        painter.drawPie(rect, start_angle, arc_length)
-                    elif self.shape == RenderArea.Path:
-                        painter.drawPath(path)
-                    elif self.shape == RenderArea.Text:
-                        qv = qVersion()
-                        painter.drawText(rect, Qt.AlignmentFlag.AlignCenter,
-                                         f"PySide 6\nQt {qv}")
-                    elif self.shape == RenderArea.Pixmap:
-                        painter.drawPixmap(10, 10, self.pixmap)
-
-                    painter.restore()
+                    with QPainterStateGuard(painter):
+                        painter.translate(x, y)
+                        if self.transformed:
+                            painter.translate(50, 50)
+                            painter.rotate(60.0)
+                            painter.scale(0.6, 0.9)
+                            painter.translate(-50, -50)
+
+                        if self.shape == RenderArea.Line:
+                            painter.drawLine(rect.bottomLeft(), rect.topRight())
+                        elif self.shape == RenderArea.Points:
+                            painter.drawPoints(RenderArea.points)
+                        elif self.shape == RenderArea.Polyline:
+                            painter.drawPolyline(RenderArea.points)
+                        elif self.shape == RenderArea.Polygon:
+                            painter.drawPolygon(RenderArea.points)
+                        elif self.shape == RenderArea.Rect:
+                            painter.drawRect(rect)
+                        elif self.shape == RenderArea.RoundedRect:
+                            painter.drawRoundedRect(rect, 25, 25, Qt.SizeMode.RelativeSize)
+                        elif self.shape == RenderArea.Ellipse:
+                            painter.drawEllipse(rect)
+                        elif self.shape == RenderArea.Arc:
+                            painter.drawArc(rect, start_angle, arc_length)
+                        elif self.shape == RenderArea.Chord:
+                            painter.drawChord(rect, start_angle, arc_length)
+                        elif self.shape == RenderArea.Pie:
+                            painter.drawPie(rect, start_angle, arc_length)
+                        elif self.shape == RenderArea.Path:
+                            painter.drawPath(path)
+                        elif self.shape == RenderArea.Text:
+                            qv = qVersion()
+                            painter.drawText(rect, Qt.AlignmentFlag.AlignCenter,
+                                             f"PySide 6\nQt {qv}")
+                        elif self.shape == RenderArea.Pixmap:
+                            painter.drawPixmap(10, 10, self.pixmap)
 
             painter.setPen(self.palette().dark().color())
             painter.setBrush(Qt.BrushStyle.NoBrush)
diff --git a/examples/widgets/tutorials/addressbook/addressbook.pyproject b/examples/widgets/tutorials/addressbook/addressbook.pyproject
deleted file mode 100644 (file)
index 13d739e..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-    "files": ["part3.py", "part1.py", "part5.py", "part2.py",
-              "part7.py", "part6.py", "part4.py"]
-}
diff --git a/examples/widgets/tutorials/addressbook/doc/addressbook.png b/examples/widgets/tutorials/addressbook/doc/addressbook.png
deleted file mode 100644 (file)
index 7d563c1..0000000
Binary files a/examples/widgets/tutorials/addressbook/doc/addressbook.png and /dev/null differ
diff --git a/examples/widgets/tutorials/addressbook/doc/addressbook.rst b/examples/widgets/tutorials/addressbook/doc/addressbook.rst
deleted file mode 100644 (file)
index 646d949..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-Address Book Example
-====================
-
-The address book example shows how to use proxy models to display different
-views onto data from a single model.
-
-.. image:: addressbook.png
-   :width: 400
-   :alt: Address Book Screenshot
diff --git a/examples/widgets/tutorials/addressbook/part1.py b/examples/widgets/tutorials/addressbook/part1.py
deleted file mode 100644 (file)
index c3785f4..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import sys
-
-from PySide6.QtCore import Qt
-from PySide6.QtWidgets import (QApplication, QGridLayout, QLabel, QLineEdit,
-                               QTextEdit, QWidget)
-
-
-class AddressBook(QWidget):
-    def __init__(self, parent=None):
-        super().__init__(parent)
-
-        name_label = QLabel("Name:")
-        self._name_line = QLineEdit()
-
-        address_label = QLabel("Address:")
-        self._address_text = QTextEdit()
-
-        main_layout = QGridLayout()
-        main_layout.addWidget(name_label, 0, 0)
-        main_layout.addWidget(self._name_line, 0, 1)
-        main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
-        main_layout.addWidget(self._address_text, 1, 1)
-
-        self.setLayout(main_layout)
-        self.setWindowTitle("Simple Address Book")
-
-
-if __name__ == '__main__':
-    app = QApplication(sys.argv)
-
-    address_book = AddressBook()
-    address_book.show()
-
-    sys.exit(app.exec())
diff --git a/examples/widgets/tutorials/addressbook/part2.py b/examples/widgets/tutorials/addressbook/part2.py
deleted file mode 100644 (file)
index 0455087..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import sys
-
-from PySide6.QtCore import Qt
-from PySide6.QtWidgets import (QApplication, QGridLayout,
-                               QLabel, QLineEdit,
-                               QMessageBox, QPushButton, QTextEdit,
-                               QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
-    class Iterator:
-        def __init__(self, sorted_dict):
-            self._dict = sorted_dict
-            self._keys = sorted(self._dict.keys())
-            self._nr_items = len(self._keys)
-            self._idx = 0
-
-        def __iter__(self):
-            return self
-
-        def next(self):
-            if self._idx >= self._nr_items:
-                raise StopIteration
-
-            key = self._keys[self._idx]
-            value = self._dict[key]
-            self._idx += 1
-
-            return key, value
-
-        __next__ = next
-
-    def __iter__(self):
-        return SortedDict.Iterator(self)
-
-    iterkeys = __iter__
-
-
-class AddressBook(QWidget):
-    def __init__(self, parent=None):
-        super().__init__(parent)
-
-        self.contacts = SortedDict()
-        self._old_name = ''
-        self._old_address = ''
-
-        name_label = QLabel("Name:")
-        self._name_line = QLineEdit()
-        self._name_line.setReadOnly(True)
-
-        address_label = QLabel("Address:")
-        self._address_text = QTextEdit()
-        self._address_text.setReadOnly(True)
-
-        self._add_button = QPushButton("&Add")
-        self._submit_button = QPushButton("&Submit")
-        self._submit_button.hide()
-        self._cancel_button = QPushButton("&Cancel")
-        self._cancel_button.hide()
-
-        self._add_button.clicked.connect(self.add_contact)
-        self._submit_button.clicked.connect(self.submit_contact)
-        self._cancel_button.clicked.connect(self.cancel)
-
-        button_layout_1 = QVBoxLayout()
-        button_layout_1.addWidget(self._add_button, Qt.AlignmentFlag.AlignTop)
-        button_layout_1.addWidget(self._submit_button)
-        button_layout_1.addWidget(self._cancel_button)
-        button_layout_1.addStretch()
-
-        main_layout = QGridLayout()
-        main_layout.addWidget(name_label, 0, 0)
-        main_layout.addWidget(self._name_line, 0, 1)
-        main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
-        main_layout.addWidget(self._address_text, 1, 1)
-        main_layout.addLayout(button_layout_1, 1, 2)
-
-        self.setLayout(main_layout)
-        self.setWindowTitle("Simple Address Book")
-
-    def add_contact(self):
-        self._old_name = self._name_line.text()
-        self._old_address = self._address_text.toPlainText()
-
-        self._name_line.clear()
-        self._address_text.clear()
-
-        self._name_line.setReadOnly(False)
-        self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
-        self._address_text.setReadOnly(False)
-
-        self._add_button.setEnabled(False)
-        self._submit_button.show()
-        self._cancel_button.show()
-
-    def submit_contact(self):
-        name = self._name_line.text()
-        address = self._address_text.toPlainText()
-
-        if name == "" or address == "":
-            QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
-            return
-
-        if name not in self.contacts:
-            self.contacts[name] = address
-            QMessageBox.information(self, "Add Successful",
-                                    f'"{name}" has been added to your address book.')
-        else:
-            QMessageBox.information(self, "Add Unsuccessful",
-                                    f'Sorry, "{name}" is already in your address book.')
-            return
-
-        if not self.contacts:
-            self._name_line.clear()
-            self._address_text.clear()
-
-        self._name_line.setReadOnly(True)
-        self._address_text.setReadOnly(True)
-        self._add_button.setEnabled(True)
-        self._submit_button.hide()
-        self._cancel_button.hide()
-
-    def cancel(self):
-        self._name_line.setText(self._old_name)
-        self._name_line.setReadOnly(True)
-
-        self._address_text.setText(self._old_address)
-        self._address_text.setReadOnly(True)
-
-        self._add_button.setEnabled(True)
-        self._submit_button.hide()
-        self._cancel_button.hide()
-
-
-if __name__ == '__main__':
-    app = QApplication(sys.argv)
-
-    address_book = AddressBook()
-    address_book.show()
-
-    sys.exit(app.exec())
diff --git a/examples/widgets/tutorials/addressbook/part3.py b/examples/widgets/tutorials/addressbook/part3.py
deleted file mode 100644 (file)
index 5083afc..0000000
+++ /dev/null
@@ -1,215 +0,0 @@
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import sys
-
-from PySide6.QtCore import Qt, Slot
-from PySide6.QtWidgets import (QApplication, QGridLayout,
-                               QHBoxLayout, QLabel, QLineEdit,
-                               QMessageBox, QPushButton, QTextEdit,
-                               QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
-    class Iterator:
-        def __init__(self, sorted_dict):
-            self._dict = sorted_dict
-            self._keys = sorted(self._dict.keys())
-            self._nr_items = len(self._keys)
-            self._idx = 0
-
-        def __iter__(self):
-            return self
-
-        def next(self):
-            if self._idx >= self._nr_items:
-                raise StopIteration
-
-            key = self._keys[self._idx]
-            value = self._dict[key]
-            self._idx += 1
-
-            return key, value
-
-        __next__ = next
-
-    def __iter__(self):
-        return SortedDict.Iterator(self)
-
-    iterkeys = __iter__
-
-
-class AddressBook(QWidget):
-    def __init__(self, parent=None):
-        super().__init__(parent)
-
-        self.contacts = SortedDict()
-        self._old_name = ''
-        self._old_address = ''
-
-        name_label = QLabel("Name:")
-        self._name_line = QLineEdit()
-        self._name_line.setReadOnly(True)
-
-        address_label = QLabel("Address:")
-        self._address_text = QTextEdit()
-        self._address_text.setReadOnly(True)
-
-        self._add_button = QPushButton("&Add")
-        self._submit_button = QPushButton("&Submit")
-        self._submit_button.hide()
-        self._cancel_button = QPushButton("&Cancel")
-        self._cancel_button.hide()
-        self._next_button = QPushButton("&Next")
-        self._next_button.setEnabled(False)
-        self._previous_button = QPushButton("&Previous")
-        self._previous_button.setEnabled(False)
-
-        self._add_button.clicked.connect(self.add_contact)
-        self._submit_button.clicked.connect(self.submit_contact)
-        self._cancel_button.clicked.connect(self.cancel)
-        self._next_button.clicked.connect(self.next)
-        self._previous_button.clicked.connect(self.previous)
-
-        button_layout_1 = QVBoxLayout()
-        button_layout_1.addWidget(self._add_button, Qt.AlignmentFlag.AlignTop)
-        button_layout_1.addWidget(self._submit_button)
-        button_layout_1.addWidget(self._cancel_button)
-        button_layout_1.addStretch()
-
-        button_layout_2 = QHBoxLayout()
-        button_layout_2.addWidget(self._previous_button)
-        button_layout_2.addWidget(self._next_button)
-
-        main_layout = QGridLayout()
-        main_layout.addWidget(name_label, 0, 0)
-        main_layout.addWidget(self._name_line, 0, 1)
-        main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
-        main_layout.addWidget(self._address_text, 1, 1)
-        main_layout.addLayout(button_layout_1, 1, 2)
-        main_layout.addLayout(button_layout_2, 3, 1)
-
-        self.setLayout(main_layout)
-        self.setWindowTitle("Simple Address Book")
-
-    def add_contact(self):
-        self._old_name = self._name_line.text()
-        self._old_address = self._address_text.toPlainText()
-
-        self._name_line.clear()
-        self._address_text.clear()
-
-        self._name_line.setReadOnly(False)
-        self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
-        self._address_text.setReadOnly(False)
-
-        self._add_button.setEnabled(False)
-        self._next_button.setEnabled(False)
-        self._previous_button.setEnabled(False)
-        self._submit_button.show()
-        self._cancel_button.show()
-
-    @Slot()
-    def submit_contact(self):
-        name = self._name_line.text()
-        address = self._address_text.toPlainText()
-
-        if name == "" or address == "":
-            QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
-            return
-
-        if name not in self.contacts:
-            self.contacts[name] = address
-            QMessageBox.information(self, "Add Successful",
-                                    f'"{name}" has been added to your address book.')
-        else:
-            QMessageBox.information(self, "Add Unsuccessful",
-                                    f'Sorry, "{name}" is already in your address book.')
-            return
-
-        if not self.contacts:
-            self._name_line.clear()
-            self._address_text.clear()
-
-        self._name_line.setReadOnly(True)
-        self._address_text.setReadOnly(True)
-        self._add_button.setEnabled(True)
-
-        number = len(self.contacts)
-        self._next_button.setEnabled(number > 1)
-        self._previous_button.setEnabled(number > 1)
-
-        self._submit_button.hide()
-        self._cancel_button.hide()
-
-    @Slot()
-    def cancel(self):
-        self._name_line.setText(self._old_name)
-        self._address_text.setText(self._old_address)
-
-        if not self.contacts:
-            self._name_line.clear()
-            self._address_text.clear()
-
-        self._name_line.setReadOnly(True)
-        self._address_text.setReadOnly(True)
-        self._add_button.setEnabled(True)
-
-        number = len(self.contacts)
-        self._next_button.setEnabled(number > 1)
-        self._previous_button.setEnabled(number > 1)
-
-        self._submit_button.hide()
-        self._cancel_button.hide()
-
-    @Slot()
-    def next(self):
-        name = self._name_line.text()
-        it = iter(self.contacts)
-
-        try:
-            while True:
-                this_name, _ = it.next()
-
-                if this_name == name:
-                    next_name, next_address = it.next()
-                    break
-        except StopIteration:
-            next_name, next_address = iter(self.contacts).next()
-
-        self._name_line.setText(next_name)
-        self._address_text.setText(next_address)
-
-    @Slot()
-    def previous(self):
-        name = self._name_line.text()
-
-        prev_name = prev_address = None
-        for this_name, this_address in self.contacts:
-            if this_name == name:
-                break
-
-            prev_name = this_name
-            prev_address = this_address
-        else:
-            self._name_line.clear()
-            self._address_text.clear()
-            return
-
-        if prev_name is None:
-            for prev_name, prev_address in self.contacts:
-                pass
-
-        self._name_line.setText(prev_name)
-        self._address_text.setText(prev_address)
-
-
-if __name__ == '__main__':
-    app = QApplication(sys.argv)
-
-    address_book = AddressBook()
-    address_book.show()
-
-    sys.exit(app.exec())
diff --git a/examples/widgets/tutorials/addressbook/part4.py b/examples/widgets/tutorials/addressbook/part4.py
deleted file mode 100644 (file)
index de4bbfa..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import sys
-
-from PySide6.QtCore import Qt, Slot
-from PySide6.QtWidgets import (QApplication, QGridLayout,
-                               QHBoxLayout, QLabel, QLineEdit,
-                               QMessageBox, QPushButton, QTextEdit,
-                               QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
-    class Iterator:
-        def __init__(self, sorted_dict):
-            self._dict = sorted_dict
-            self._keys = sorted(self._dict.keys())
-            self._nr_items = len(self._keys)
-            self._idx = 0
-
-        def __iter__(self):
-            return self
-
-        def next(self):
-            if self._idx >= self._nr_items:
-                raise StopIteration
-
-            key = self._keys[self._idx]
-            value = self._dict[key]
-            self._idx += 1
-
-            return key, value
-
-        __next__ = next
-
-    def __iter__(self):
-        return SortedDict.Iterator(self)
-
-    iterkeys = __iter__
-
-
-class AddressBook(QWidget):
-    NavigationMode, AddingMode, EditingMode = range(3)
-
-    def __init__(self, parent=None):
-        super().__init__(parent)
-
-        self.contacts = SortedDict()
-        self._old_name = ''
-        self._old_address = ''
-        self._current_mode = self.NavigationMode
-
-        name_label = QLabel("Name:")
-        self._name_line = QLineEdit()
-        self._name_line.setReadOnly(True)
-
-        address_label = QLabel("Address:")
-        self._address_text = QTextEdit()
-        self._address_text.setReadOnly(True)
-
-        self._add_button = QPushButton("&Add")
-        self._edit_button = QPushButton("&Edit")
-        self._edit_button.setEnabled(False)
-        self._remove_button = QPushButton("&Remove")
-        self._remove_button.setEnabled(False)
-        self._submit_button = QPushButton("&Submit")
-        self._submit_button.hide()
-        self._cancel_button = QPushButton("&Cancel")
-        self._cancel_button.hide()
-
-        self._next_button = QPushButton("&Next")
-        self._next_button.setEnabled(False)
-        self._previous_button = QPushButton("&Previous")
-        self._previous_button.setEnabled(False)
-
-        self._add_button.clicked.connect(self.add_contact)
-        self._submit_button.clicked.connect(self.submit_contact)
-        self._edit_button.clicked.connect(self.edit_contact)
-        self._remove_button.clicked.connect(self.remove_contact)
-        self._cancel_button.clicked.connect(self.cancel)
-        self._next_button.clicked.connect(self.next)
-        self._previous_button.clicked.connect(self.previous)
-
-        button_layout_1 = QVBoxLayout()
-        button_layout_1.addWidget(self._add_button)
-        button_layout_1.addWidget(self._edit_button)
-        button_layout_1.addWidget(self._remove_button)
-        button_layout_1.addWidget(self._submit_button)
-        button_layout_1.addWidget(self._cancel_button)
-        button_layout_1.addStretch()
-
-        button_layout_2 = QHBoxLayout()
-        button_layout_2.addWidget(self._previous_button)
-        button_layout_2.addWidget(self._next_button)
-
-        main_layout = QGridLayout()
-        main_layout.addWidget(name_label, 0, 0)
-        main_layout.addWidget(self._name_line, 0, 1)
-        main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
-        main_layout.addWidget(self._address_text, 1, 1)
-        main_layout.addLayout(button_layout_1, 1, 2)
-        main_layout.addLayout(button_layout_2, 3, 1)
-
-        self.setLayout(main_layout)
-        self.setWindowTitle("Simple Address Book")
-
-    @Slot()
-    def add_contact(self):
-        self._old_name = self._name_line.text()
-        self._old_address = self._address_text.toPlainText()
-
-        self._name_line.clear()
-        self._address_text.clear()
-
-        self.update_interface(self.AddingMode)
-
-    @Slot()
-    def edit_contact(self):
-        self._old_name = self._name_line.text()
-        self._old_address = self._address_text.toPlainText()
-
-        self.update_interface(self.EditingMode)
-
-    @Slot()
-    def submit_contact(self):
-        name = self._name_line.text()
-        address = self._address_text.toPlainText()
-
-        if name == "" or address == "":
-            QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
-            return
-
-        if self._current_mode == self.AddingMode:
-            if name not in self.contacts:
-                self.contacts[name] = address
-                QMessageBox.information(self, "Add Successful",
-                                        f'"{name}" has been added to your address book.')
-            else:
-                QMessageBox.information(self, "Add Unsuccessful",
-                                        f'Sorry, "{name}" is already in your address book.')
-                return
-
-        elif self._current_mode == self.EditingMode:
-            if self._old_name != name:
-                if name not in self.contacts:
-                    QMessageBox.information(self, "Edit Successful",
-                                            f'"{self.oldName}" has been edited in your '
-                                            'address book.')
-                    del self.contacts[self._old_name]
-                    self.contacts[name] = address
-                else:
-                    QMessageBox.information(self, "Edit Unsuccessful",
-                                            f'Sorry, "{name}" is already in your address book.')
-                    return
-            elif self._old_address != address:
-                QMessageBox.information(self, "Edit Successful",
-                                        f'"{name}" has been edited in your address book.')
-                self.contacts[name] = address
-
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def cancel(self):
-        self._name_line.setText(self._old_name)
-        self._address_text.setText(self._old_address)
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def remove_contact(self):
-        name = self._name_line.text()
-
-        if name in self.contacts:
-            button = QMessageBox.question(self, "Confirm Remove",
-                                          f'Are you sure you want to remove "{name}"?',
-                                          QMessageBox.Yes | QMessageBox.No)
-
-            if button == QMessageBox.Yes:
-                self.previous()
-                del self.contacts[name]
-
-                QMessageBox.information(self, "Remove Successful",
-                                        f'"{name}" has been removed from your address book.')
-
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def next(self):
-        name = self._name_line.text()
-        it = iter(self.contacts)
-
-        try:
-            while True:
-                this_name, _ = it.next()
-
-                if this_name == name:
-                    next_name, next_address = it.next()
-                    break
-        except StopIteration:
-            next_name, next_address = iter(self.contacts).next()
-
-        self._name_line.setText(next_name)
-        self._address_text.setText(next_address)
-
-    @Slot()
-    def previous(self):
-        name = self._name_line.text()
-
-        prev_name = prev_address = None
-        for this_name, this_address in self.contacts:
-            if this_name == name:
-                break
-
-            prev_name = this_name
-            prev_address = this_address
-        else:
-            self._name_line.clear()
-            self._address_text.clear()
-            return
-
-        if prev_name is None:
-            for prev_name, prev_address in self.contacts:
-                pass
-
-        self._name_line.setText(prev_name)
-        self._address_text.setText(prev_address)
-
-    def update_interface(self, mode):
-        self._current_mode = mode
-
-        if self._current_mode in (self.AddingMode, self.EditingMode):
-            self._name_line.setReadOnly(False)
-            self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
-            self._address_text.setReadOnly(False)
-
-            self._add_button.setEnabled(False)
-            self._edit_button.setEnabled(False)
-            self._remove_button.setEnabled(False)
-
-            self._next_button.setEnabled(False)
-            self._previous_button.setEnabled(False)
-
-            self._submit_button.show()
-            self._cancel_button.show()
-
-        elif self._current_mode == self.NavigationMode:
-            if not self.contacts:
-                self._name_line.clear()
-                self._address_text.clear()
-
-            self._name_line.setReadOnly(True)
-            self._address_text.setReadOnly(True)
-            self._add_button.setEnabled(True)
-
-            number = len(self.contacts)
-            self._edit_button.setEnabled(number >= 1)
-            self._remove_button.setEnabled(number >= 1)
-            self._next_button.setEnabled(number > 1)
-            self._previous_button.setEnabled(number > 1)
-
-            self._submit_button.hide()
-            self._cancel_button.hide()
-
-
-if __name__ == '__main__':
-    app = QApplication(sys.argv)
-
-    address_book = AddressBook()
-    address_book.show()
-
-    sys.exit(app.exec())
diff --git a/examples/widgets/tutorials/addressbook/part5.py b/examples/widgets/tutorials/addressbook/part5.py
deleted file mode 100644 (file)
index 1353820..0000000
+++ /dev/null
@@ -1,331 +0,0 @@
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import sys
-
-from PySide6.QtCore import Qt, Slot
-from PySide6.QtWidgets import (QApplication, QDialog, QGridLayout,
-                               QHBoxLayout, QLabel, QLineEdit,
-                               QMessageBox, QPushButton, QTextEdit,
-                               QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
-    class Iterator:
-        def __init__(self, sorted_dict):
-            self._dict = sorted_dict
-            self._keys = sorted(self._dict.keys())
-            self._nr_items = len(self._keys)
-            self._idx = 0
-
-        def __iter__(self):
-            return self
-
-        def next(self):
-            if self._idx >= self._nr_items:
-                raise StopIteration
-
-            key = self._keys[self._idx]
-            value = self._dict[key]
-            self._idx += 1
-
-            return key, value
-
-        __next__ = next
-
-    def __iter__(self):
-        return SortedDict.Iterator(self)
-
-    iterkeys = __iter__
-
-
-class AddressBook(QWidget):
-    NavigationMode, AddingMode, EditingMode = range(3)
-
-    def __init__(self, parent=None):
-        super().__init__(parent)
-
-        self.contacts = SortedDict()
-        self._old_name = ''
-        self._old_address = ''
-        self._current_mode = self.NavigationMode
-
-        name_label = QLabel("Name:")
-        self._name_line = QLineEdit()
-        self._name_line.setReadOnly(True)
-
-        address_label = QLabel("Address:")
-        self._address_text = QTextEdit()
-        self._address_text.setReadOnly(True)
-
-        self._add_button = QPushButton("&Add")
-        self._edit_button = QPushButton("&Edit")
-        self._edit_button.setEnabled(False)
-        self._remove_button = QPushButton("&Remove")
-        self._remove_button.setEnabled(False)
-        self._find_button = QPushButton("&Find")
-        self._find_button.setEnabled(False)
-        self._submit_button = QPushButton("&Submit")
-        self._submit_button.hide()
-        self._cancel_button = QPushButton("&Cancel")
-        self._cancel_button.hide()
-
-        self._next_button = QPushButton("&Next")
-        self._next_button.setEnabled(False)
-        self._previous_button = QPushButton("&Previous")
-        self._previous_button.setEnabled(False)
-
-        self.dialog = FindDialog()
-
-        self._add_button.clicked.connect(self.add_contact)
-        self._submit_button.clicked.connect(self.submit_contact)
-        self._edit_button.clicked.connect(self.edit_contact)
-        self._remove_button.clicked.connect(self.remove_contact)
-        self._find_button.clicked.connect(self.find_contact)
-        self._cancel_button.clicked.connect(self.cancel)
-        self._next_button.clicked.connect(self.next)
-        self._previous_button.clicked.connect(self.previous)
-
-        button_layout_1 = QVBoxLayout()
-        button_layout_1.addWidget(self._add_button)
-        button_layout_1.addWidget(self._edit_button)
-        button_layout_1.addWidget(self._remove_button)
-        button_layout_1.addWidget(self._find_button)
-        button_layout_1.addWidget(self._submit_button)
-        button_layout_1.addWidget(self._cancel_button)
-        button_layout_1.addStretch()
-
-        button_layout_2 = QHBoxLayout()
-        button_layout_2.addWidget(self._previous_button)
-        button_layout_2.addWidget(self._next_button)
-
-        main_layout = QGridLayout()
-        main_layout.addWidget(name_label, 0, 0)
-        main_layout.addWidget(self._name_line, 0, 1)
-        main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
-        main_layout.addWidget(self._address_text, 1, 1)
-        main_layout.addLayout(button_layout_1, 1, 2)
-        main_layout.addLayout(button_layout_2, 2, 1)
-
-        self.setLayout(main_layout)
-        self.setWindowTitle("Simple Address Book")
-
-    @Slot()
-    def add_contact(self):
-        self._old_name = self._name_line.text()
-        self._old_address = self._address_text.toPlainText()
-
-        self._name_line.clear()
-        self._address_text.clear()
-
-        self.update_interface(self.AddingMode)
-
-    @Slot()
-    def edit_contact(self):
-        self._old_name = self._name_line.text()
-        self._old_address = self._address_text.toPlainText()
-
-        self.update_interface(self.EditingMode)
-
-    @Slot()
-    def submit_contact(self):
-        name = self._name_line.text()
-        address = self._address_text.toPlainText()
-
-        if name == "" or address == "":
-            QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
-            return
-
-        if self._current_mode == self.AddingMode:
-            if name not in self.contacts:
-                self.contacts[name] = address
-                QMessageBox.information(self, "Add Successful",
-                                        f'"{name}" has been added to your address book.')
-            else:
-                QMessageBox.information(self, "Add Unsuccessful",
-                                        f'Sorry, "{name}" is already in your address book.')
-                return
-
-        elif self._current_mode == self.EditingMode:
-            if self._old_name != name:
-                if name not in self.contacts:
-                    QMessageBox.information(self, "Edit Successful",
-                                            f'"{self.oldName}" has been edited in your '
-                                            'address book.')
-                    del self.contacts[self._old_name]
-                    self.contacts[name] = address
-                else:
-                    QMessageBox.information(self, "Edit Unsuccessful",
-                                            f'Sorry, "{name}" is already in your address book.')
-                    return
-            elif self._old_address != address:
-                QMessageBox.information(self, "Edit Successful",
-                                        f'"{name}" has been edited in your address book.')
-                self.contacts[name] = address
-
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def cancel(self):
-        self._name_line.setText(self._old_name)
-        self._address_text.setText(self._old_address)
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def remove_contact(self):
-        name = self._name_line.text()
-
-        if name in self.contacts:
-            button = QMessageBox.question(self, "Confirm Remove",
-                                          f'Are you sure you want to remove "{name}"?',
-                                          QMessageBox.Yes | QMessageBox.No)
-
-            if button == QMessageBox.Yes:
-                self.previous()
-                del self.contacts[name]
-
-                QMessageBox.information(self, "Remove Successful",
-                                        f'"{name}" has been removed from your address book.')
-
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def next(self):
-        name = self._name_line.text()
-        it = iter(self.contacts)
-
-        try:
-            while True:
-                this_name, _ = it.next()
-
-                if this_name == name:
-                    next_name, next_address = it.next()
-                    break
-        except StopIteration:
-            next_name, next_address = iter(self.contacts).next()
-
-        self._name_line.setText(next_name)
-        self._address_text.setText(next_address)
-
-    @Slot()
-    def previous(self):
-        name = self._name_line.text()
-
-        prev_name = prev_address = None
-        for this_name, this_address in self.contacts:
-            if this_name == name:
-                break
-
-            prev_name = this_name
-            prev_address = this_address
-        else:
-            self._name_line.clear()
-            self._address_text.clear()
-            return
-
-        if prev_name is None:
-            for prev_name, prev_address in self.contacts:
-                pass
-
-        self._name_line.setText(prev_name)
-        self._address_text.setText(prev_address)
-
-    def find_contact(self):
-        self.dialog.show()
-
-        if self.dialog.exec() == QDialog.Accepted:
-            contact_name = self.dialog.get_find_text()
-
-            if contact_name in self.contacts:
-                self._name_line.setText(contact_name)
-                self._address_text.setText(self.contacts[contact_name])
-            else:
-                QMessageBox.information(self, "Contact Not Found",
-                                        f'Sorry, "{contact_name}" is not in your address book.')
-                return
-
-        self.update_interface(self.NavigationMode)
-
-    def update_interface(self, mode):
-        self._current_mode = mode
-
-        if self._current_mode in (self.AddingMode, self.EditingMode):
-            self._name_line.setReadOnly(False)
-            self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
-            self._address_text.setReadOnly(False)
-
-            self._add_button.setEnabled(False)
-            self._edit_button.setEnabled(False)
-            self._remove_button.setEnabled(False)
-
-            self._next_button.setEnabled(False)
-            self._previous_button.setEnabled(False)
-
-            self._submit_button.show()
-            self._cancel_button.show()
-
-        elif self._current_mode == self.NavigationMode:
-            if not self.contacts:
-                self._name_line.clear()
-                self._address_text.clear()
-
-            self._name_line.setReadOnly(True)
-            self._address_text.setReadOnly(True)
-            self._add_button.setEnabled(True)
-
-            number = len(self.contacts)
-            self._edit_button.setEnabled(number >= 1)
-            self._remove_button.setEnabled(number >= 1)
-            self._find_button.setEnabled(number > 2)
-            self._next_button.setEnabled(number > 1)
-            self._previous_button.setEnabled(number > 1)
-
-            self._submit_button.hide()
-            self._cancel_button.hide()
-
-
-class FindDialog(QDialog):
-    def __init__(self, parent=None):
-        super().__init__(parent)
-
-        find_label = QLabel("Enter the name of a contact:")
-        self._line_edit = QLineEdit()
-
-        self._find_button = QPushButton("&Find")
-        self._find_text = ''
-
-        layout = QHBoxLayout()
-        layout.addWidget(find_label)
-        layout.addWidget(self._line_edit)
-        layout.addWidget(self._find_button)
-
-        self.setLayout(layout)
-        self.setWindowTitle("Find a Contact")
-
-        self._find_button.clicked.connect(self.find_clicked)
-        self._find_button.clicked.connect(self.accept)
-
-    def find_clicked(self):
-        text = self._line_edit.text()
-
-        if not text:
-            QMessageBox.information(self, "Empty Field", "Please enter a name.")
-            return
-        else:
-            self._find_text = text
-            self._line_edit.clear()
-            self.hide()
-
-    def get_find_text(self):
-        return self._find_text
-
-
-if __name__ == '__main__':
-    app = QApplication(sys.argv)
-
-    address_book = AddressBook()
-    address_book.show()
-
-    sys.exit(app.exec())
diff --git a/examples/widgets/tutorials/addressbook/part6.py b/examples/widgets/tutorials/addressbook/part6.py
deleted file mode 100644 (file)
index 73dcd06..0000000
+++ /dev/null
@@ -1,394 +0,0 @@
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import pickle
-import sys
-
-from PySide6.QtCore import Qt, Slot
-from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog,
-                               QGridLayout, QHBoxLayout, QLabel, QLineEdit,
-                               QMessageBox, QPushButton, QTextEdit,
-                               QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
-    class Iterator:
-        def __init__(self, sorted_dict):
-            self._dict = sorted_dict
-            self._keys = sorted(self._dict.keys())
-            self._nr_items = len(self._keys)
-            self._idx = 0
-
-        def __iter__(self):
-            return self
-
-        def next(self):
-            if self._idx >= self._nr_items:
-                raise StopIteration
-
-            key = self._keys[self._idx]
-            value = self._dict[key]
-            self._idx += 1
-
-            return key, value
-
-        __next__ = next
-
-    def __iter__(self):
-        return SortedDict.Iterator(self)
-
-    iterkeys = __iter__
-
-
-class AddressBook(QWidget):
-    NavigationMode, AddingMode, EditingMode = range(3)
-
-    def __init__(self, parent=None):
-        super().__init__(parent)
-
-        self.contacts = SortedDict()
-        self._old_name = ''
-        self._old_address = ''
-        self._current_mode = self.NavigationMode
-
-        name_label = QLabel("Name:")
-        self._name_line = QLineEdit()
-        self._name_line.setReadOnly(True)
-
-        address_label = QLabel("Address:")
-        self._address_text = QTextEdit()
-        self._address_text.setReadOnly(True)
-
-        self._add_button = QPushButton("&Add")
-        self._edit_button = QPushButton("&Edit")
-        self._edit_button.setEnabled(False)
-        self._remove_button = QPushButton("&Remove")
-        self._remove_button.setEnabled(False)
-        self._find_button = QPushButton("&Find")
-        self._find_button.setEnabled(False)
-        self._submit_button = QPushButton("&Submit")
-        self._submit_button.hide()
-        self._cancel_button = QPushButton("&Cancel")
-        self._cancel_button.hide()
-
-        self._next_button = QPushButton("&Next")
-        self._next_button.setEnabled(False)
-        self._previous_button = QPushButton("&Previous")
-        self._previous_button.setEnabled(False)
-
-        self._load_button = QPushButton("&Load...")
-        self._load_button.setToolTip("Load contacts from a file")
-        self._save_button = QPushButton("Sa&ve...")
-        self._save_button.setToolTip("Save contacts to a file")
-        self._save_button.setEnabled(False)
-
-        self.dialog = FindDialog()
-
-        self._add_button.clicked.connect(self.add_contact)
-        self._submit_button.clicked.connect(self.submit_contact)
-        self._edit_button.clicked.connect(self.edit_contact)
-        self._remove_button.clicked.connect(self.remove_contact)
-        self._find_button.clicked.connect(self.find_contact)
-        self._cancel_button.clicked.connect(self.cancel)
-        self._next_button.clicked.connect(self.next)
-        self._previous_button.clicked.connect(self.previous)
-        self._load_button.clicked.connect(self.load_from_file)
-        self._save_button.clicked.connect(self.save_to_file)
-
-        button_layout_1 = QVBoxLayout()
-        button_layout_1.addWidget(self._add_button)
-        button_layout_1.addWidget(self._edit_button)
-        button_layout_1.addWidget(self._remove_button)
-        button_layout_1.addWidget(self._find_button)
-        button_layout_1.addWidget(self._submit_button)
-        button_layout_1.addWidget(self._cancel_button)
-        button_layout_1.addWidget(self._load_button)
-        button_layout_1.addWidget(self._save_button)
-        button_layout_1.addStretch()
-
-        button_layout_2 = QHBoxLayout()
-        button_layout_2.addWidget(self._previous_button)
-        button_layout_2.addWidget(self._next_button)
-
-        main_layout = QGridLayout()
-        main_layout.addWidget(name_label, 0, 0)
-        main_layout.addWidget(self._name_line, 0, 1)
-        main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
-        main_layout.addWidget(self._address_text, 1, 1)
-        main_layout.addLayout(button_layout_1, 1, 2)
-        main_layout.addLayout(button_layout_2, 2, 1)
-
-        self.setLayout(main_layout)
-        self.setWindowTitle("Simple Address Book")
-
-    @Slot()
-    def add_contact(self):
-        self._old_name = self._name_line.text()
-        self._old_address = self._address_text.toPlainText()
-
-        self._name_line.clear()
-        self._address_text.clear()
-
-        self.update_interface(self.AddingMode)
-
-    @Slot()
-    def edit_contact(self):
-        self._old_name = self._name_line.text()
-        self._old_address = self._address_text.toPlainText()
-
-        self.update_interface(self.EditingMode)
-
-    @Slot()
-    def submit_contact(self):
-        name = self._name_line.text()
-        address = self._address_text.toPlainText()
-
-        if name == "" or address == "":
-            QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
-            return
-
-        if self._current_mode == self.AddingMode:
-            if name not in self.contacts:
-                self.contacts[name] = address
-                QMessageBox.information(self, "Add Successful",
-                                        f'"{name}" has been added to your address book.')
-            else:
-                QMessageBox.information(self, "Add Unsuccessful",
-                                        f'Sorry, "{name}" is already in your address book.')
-                return
-
-        elif self._current_mode == self.EditingMode:
-            if self._old_name != name:
-                if name not in self.contacts:
-                    QMessageBox.information(self, "Edit Successful",
-                                            f'"{self.oldName}" has been edited in your '
-                                            'address book.')
-                    del self.contacts[self._old_name]
-                    self.contacts[name] = address
-                else:
-                    QMessageBox.information(self, "Edit Unsuccessful",
-                                            f'Sorry, "{name}" is already in your address book.')
-                    return
-            elif self._old_address != address:
-                QMessageBox.information(self, "Edit Successful",
-                                        f'"{name}" has been edited in your address book.')
-                self.contacts[name] = address
-
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def cancel(self):
-        self._name_line.setText(self._old_name)
-        self._address_text.setText(self._old_address)
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def remove_contact(self):
-        name = self._name_line.text()
-
-        if name in self.contacts:
-            button = QMessageBox.question(self, "Confirm Remove",
-                                          f'Are you sure you want to remove "{name}"?',
-                                          QMessageBox.Yes | QMessageBox.No)
-
-            if button == QMessageBox.Yes:
-                self.previous()
-                del self.contacts[name]
-
-                QMessageBox.information(self, "Remove Successful",
-                                        f'"{name}" has been removed from your address book.')
-
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def next(self):
-        name = self._name_line.text()
-        it = iter(self.contacts)
-
-        try:
-            while True:
-                this_name, _ = it.next()
-
-                if this_name == name:
-                    next_name, next_address = it.next()
-                    break
-        except StopIteration:
-            next_name, next_address = iter(self.contacts).next()
-
-        self._name_line.setText(next_name)
-        self._address_text.setText(next_address)
-
-    @Slot()
-    def previous(self):
-        name = self._name_line.text()
-
-        prev_name = prev_address = None
-        for this_name, this_address in self.contacts:
-            if this_name == name:
-                break
-
-            prev_name = this_name
-            prev_address = this_address
-        else:
-            self._name_line.clear()
-            self._address_text.clear()
-            return
-
-        if prev_name is None:
-            for prev_name, prev_address in self.contacts:
-                pass
-
-        self._name_line.setText(prev_name)
-        self._address_text.setText(prev_address)
-
-    def find_contact(self):
-        self.dialog.show()
-
-        if self.dialog.exec() == QDialog.Accepted:
-            contact_name = self.dialog.get_find_text()
-
-            if contact_name in self.contacts:
-                self._name_line.setText(contact_name)
-                self._address_text.setText(self.contacts[contact_name])
-            else:
-                QMessageBox.information(self, "Contact Not Found",
-                                        f'Sorry, "{contact_name}" is not in your address book.')
-                return
-
-        self.update_interface(self.NavigationMode)
-
-    def update_interface(self, mode):
-        self._current_mode = mode
-
-        if self._current_mode in (self.AddingMode, self.EditingMode):
-            self._name_line.setReadOnly(False)
-            self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
-            self._address_text.setReadOnly(False)
-
-            self._add_button.setEnabled(False)
-            self._edit_button.setEnabled(False)
-            self._remove_button.setEnabled(False)
-
-            self._next_button.setEnabled(False)
-            self._previous_button.setEnabled(False)
-
-            self._submit_button.show()
-            self._cancel_button.show()
-
-            self._load_button.setEnabled(False)
-            self._save_button.setEnabled(False)
-
-        elif self._current_mode == self.NavigationMode:
-            if not self.contacts:
-                self._name_line.clear()
-                self._address_text.clear()
-
-            self._name_line.setReadOnly(True)
-            self._address_text.setReadOnly(True)
-            self._add_button.setEnabled(True)
-
-            number = len(self.contacts)
-            self._edit_button.setEnabled(number >= 1)
-            self._remove_button.setEnabled(number >= 1)
-            self._find_button.setEnabled(number > 2)
-            self._next_button.setEnabled(number > 1)
-            self._previous_button.setEnabled(number > 1)
-
-            self._submit_button.hide()
-            self._cancel_button.hide()
-
-            self._load_button.setEnabled(True)
-            self._save_button.setEnabled(number >= 1)
-
-    def save_to_file(self):
-        fileName, _ = QFileDialog.getSaveFileName(self,
-                                                  "Save Address Book", '',
-                                                  "Address Book (*.abk);;All Files (*)")
-
-        if not fileName:
-            return
-
-        try:
-            out_file = open(str(fileName), 'wb')
-        except IOError:
-            QMessageBox.information(self, "Unable to open file",
-                                    f'There was an error opening "{fileName}"')
-            return
-
-        pickle.dump(self.contacts, out_file)
-        out_file.close()
-
-    def load_from_file(self):
-        fileName, _ = QFileDialog.getOpenFileName(self,
-                                                  "Open Address Book", '',
-                                                  "Address Book (*.abk);;All Files (*)")
-
-        if not fileName:
-            return
-
-        try:
-            in_file = open(str(fileName), 'rb')
-        except IOError:
-            QMessageBox.information(self, "Unable to open file",
-                                    f'There was an error opening "{fileName}"')
-            return
-
-        self.contacts = pickle.load(in_file)
-        in_file.close()
-
-        if len(self.contacts) == 0:
-            QMessageBox.information(self, "No contacts in file",
-                                    "The file you are attempting to open contains no contacts.")
-        else:
-            for name, address in self.contacts:
-                self._name_line.setText(name)
-                self._address_text.setText(address)
-
-        self.update_interface(self.NavigationMode)
-
-
-class FindDialog(QDialog):
-    def __init__(self, parent=None):
-        super().__init__(parent)
-
-        find_label = QLabel("Enter the name of a contact:")
-        self._line_edit = QLineEdit()
-
-        self._find_button = QPushButton("&Find")
-        self._find_text = ''
-
-        layout = QHBoxLayout()
-        layout.addWidget(find_label)
-        layout.addWidget(self._line_edit)
-        layout.addWidget(self._find_button)
-
-        self.setLayout(layout)
-        self.setWindowTitle("Find a Contact")
-
-        self._find_button.clicked.connect(self.find_clicked)
-        self._find_button.clicked.connect(self.accept)
-
-    def find_clicked(self):
-        text = self._line_edit.text()
-
-        if not text:
-            QMessageBox.information(self, "Empty Field", "Please enter a name.")
-            return
-
-        self._find_text = text
-        self._line_edit.clear()
-        self.hide()
-
-    def get_find_text(self):
-        return self._find_text
-
-
-if __name__ == '__main__':
-    app = QApplication(sys.argv)
-
-    address_book = AddressBook()
-    address_book.show()
-
-    sys.exit(app.exec())
diff --git a/examples/widgets/tutorials/addressbook/part7.py b/examples/widgets/tutorials/addressbook/part7.py
deleted file mode 100644 (file)
index 12e2055..0000000
+++ /dev/null
@@ -1,445 +0,0 @@
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import pickle
-import sys
-
-from PySide6.QtCore import QFile, QIODevice, QTextStream, Qt, Slot
-from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog,
-                               QGridLayout, QHBoxLayout, QLabel, QLineEdit,
-                               QMessageBox, QPushButton, QTextEdit,
-                               QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
-    class Iterator:
-        def __init__(self, sorted_dict):
-            self._dict = sorted_dict
-            self._keys = sorted(self._dict.keys())
-            self._nr_items = len(self._keys)
-            self._idx = 0
-
-        def __iter__(self):
-            return self
-
-        def next(self):
-            if self._idx >= self._nr_items:
-                raise StopIteration
-
-            key = self._keys[self._idx]
-            value = self._dict[key]
-            self._idx += 1
-
-            return key, value
-
-        __next__ = next
-
-    def __iter__(self):
-        return SortedDict.Iterator(self)
-
-    iterkeys = __iter__
-
-
-class AddressBook(QWidget):
-    NavigationMode, AddingMode, EditingMode = range(3)
-
-    def __init__(self, parent=None):
-        super().__init__(parent)
-
-        self.contacts = SortedDict()
-        self._old_name = ''
-        self._old_address = ''
-        self._current_mode = self.NavigationMode
-
-        name_label = QLabel("Name:")
-        self._name_line = QLineEdit()
-        self._name_line.setReadOnly(True)
-
-        address_label = QLabel("Address:")
-        self._address_text = QTextEdit()
-        self._address_text.setReadOnly(True)
-
-        self._add_button = QPushButton("&Add")
-        self._edit_button = QPushButton("&Edit")
-        self._edit_button.setEnabled(False)
-        self._remove_button = QPushButton("&Remove")
-        self._remove_button.setEnabled(False)
-        self._find_button = QPushButton("&Find")
-        self._find_button.setEnabled(False)
-        self._submit_button = QPushButton("&Submit")
-        self._submit_button.hide()
-        self._cancel_button = QPushButton("&Cancel")
-        self._cancel_button.hide()
-
-        self._next_button = QPushButton("&Next")
-        self._next_button.setEnabled(False)
-        self._previous_button = QPushButton("&Previous")
-        self._previous_button.setEnabled(False)
-
-        self._load_button = QPushButton("&Load...")
-        self._load_button.setToolTip("Load contacts from a file")
-        self._save_button = QPushButton("Sa&ve...")
-        self._save_button.setToolTip("Save contacts to a file")
-        self._save_button.setEnabled(False)
-
-        self._export_button = QPushButton("Ex&port")
-        self._export_button.setToolTip("Export as vCard")
-        self._export_button.setEnabled(False)
-
-        self.dialog = FindDialog()
-
-        self._add_button.clicked.connect(self.add_contact)
-        self._submit_button.clicked.connect(self.submit_contact)
-        self._edit_button.clicked.connect(self.edit_contact)
-        self._remove_button.clicked.connect(self.remove_contact)
-        self._find_button.clicked.connect(self.find_contact)
-        self._cancel_button.clicked.connect(self.cancel)
-        self._next_button.clicked.connect(self.next)
-        self._previous_button.clicked.connect(self.previous)
-        self._load_button.clicked.connect(self.load_from_file)
-        self._save_button.clicked.connect(self.save_to_file)
-        self._export_button.clicked.connect(self.export_as_vcard)
-
-        button_layout_1 = QVBoxLayout()
-        button_layout_1.addWidget(self._add_button)
-        button_layout_1.addWidget(self._edit_button)
-        button_layout_1.addWidget(self._remove_button)
-        button_layout_1.addWidget(self._find_button)
-        button_layout_1.addWidget(self._submit_button)
-        button_layout_1.addWidget(self._cancel_button)
-        button_layout_1.addWidget(self._load_button)
-        button_layout_1.addWidget(self._save_button)
-        button_layout_1.addWidget(self._export_button)
-        button_layout_1.addStretch()
-
-        button_layout_2 = QHBoxLayout()
-        button_layout_2.addWidget(self._previous_button)
-        button_layout_2.addWidget(self._next_button)
-
-        main_layout = QGridLayout()
-        main_layout.addWidget(name_label, 0, 0)
-        main_layout.addWidget(self._name_line, 0, 1)
-        main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
-        main_layout.addWidget(self._address_text, 1, 1)
-        main_layout.addLayout(button_layout_1, 1, 2)
-        main_layout.addLayout(button_layout_2, 2, 1)
-
-        self.setLayout(main_layout)
-        self.setWindowTitle("Simple Address Book")
-
-    @Slot()
-    def add_contact(self):
-        self._old_name = self._name_line.text()
-        self._old_address = self._address_text.toPlainText()
-
-        self._name_line.clear()
-        self._address_text.clear()
-
-        self.update_interface(self.AddingMode)
-
-    @Slot()
-    def edit_contact(self):
-        self._old_name = self._name_line.text()
-        self._old_address = self._address_text.toPlainText()
-
-        self.update_interface(self.EditingMode)
-
-    @Slot()
-    def submit_contact(self):
-        name = self._name_line.text()
-        address = self._address_text.toPlainText()
-
-        if name == "" or address == "":
-            QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
-            return
-
-        if self._current_mode == self.AddingMode:
-            if name not in self.contacts:
-                self.contacts[name] = address
-                QMessageBox.information(self, "Add Successful",
-                                        f'"{name}" has been added to your address book.')
-            else:
-                QMessageBox.information(self, "Add Unsuccessful",
-                                        f'Sorry, "{name}" is already in your address book.')
-                return
-
-        elif self._current_mode == self.EditingMode:
-            if self._old_name != name:
-                if name not in self.contacts:
-                    QMessageBox.information(self, "Edit Successful",
-                                            f'"{self.oldName}" has been edited in your '
-                                            'address book.')
-                    del self.contacts[self._old_name]
-                    self.contacts[name] = address
-                else:
-                    QMessageBox.information(self, "Edit Unsuccessful",
-                                            f'Sorry, "{name}" is already in your address book.')
-                    return
-            elif self._old_address != address:
-                QMessageBox.information(self, "Edit Successful",
-                                        f'"{name}" has been edited in your address book.')
-                self.contacts[name] = address
-
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def cancel(self):
-        self._name_line.setText(self._old_name)
-        self._address_text.setText(self._old_address)
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def remove_contact(self):
-        name = self._name_line.text()
-
-        if name in self.contacts:
-            button = QMessageBox.question(self, "Confirm Remove",
-                                          f'Are you sure you want to remove "{name}"?',
-                                          QMessageBox.Yes | QMessageBox.No)
-
-            if button == QMessageBox.Yes:
-                self.previous()
-                del self.contacts[name]
-
-                QMessageBox.information(self, "Remove Successful",
-                                        f'"{name}" has been removed from your address book.')
-
-        self.update_interface(self.NavigationMode)
-
-    @Slot()
-    def next(self):
-        name = self._name_line.text()
-        it = iter(self.contacts)
-
-        try:
-            while True:
-                this_name, _ = it.next()
-
-                if this_name == name:
-                    next_name, next_address = it.next()
-                    break
-        except StopIteration:
-            next_name, next_address = iter(self.contacts).next()
-
-        self._name_line.setText(next_name)
-        self._address_text.setText(next_address)
-
-    @Slot()
-    def previous(self):
-        name = self._name_line.text()
-
-        prev_name = prev_address = None
-        for this_name, this_address in self.contacts:
-            if this_name == name:
-                break
-
-            prev_name = this_name
-            prev_address = this_address
-        else:
-            self._name_line.clear()
-            self._address_text.clear()
-            return
-
-        if prev_name is None:
-            for prev_name, prev_address in self.contacts:
-                pass
-
-        self._name_line.setText(prev_name)
-        self._address_text.setText(prev_address)
-
-    def find_contact(self):
-        self.dialog.show()
-
-        if self.dialog.exec() == QDialog.Accepted:
-            contact_name = self.dialog.get_find_text()
-
-            if contact_name in self.contacts:
-                self._name_line.setText(contact_name)
-                self._address_text.setText(self.contacts[contact_name])
-            else:
-                QMessageBox.information(self, "Contact Not Found",
-                                        f'Sorry, "{contact_name}" is not in your address book.')
-                return
-
-        self.update_interface(self.NavigationMode)
-
-    def update_interface(self, mode):
-        self._current_mode = mode
-
-        if self._current_mode in (self.AddingMode, self.EditingMode):
-            self._name_line.setReadOnly(False)
-            self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
-            self._address_text.setReadOnly(False)
-
-            self._add_button.setEnabled(False)
-            self._edit_button.setEnabled(False)
-            self._remove_button.setEnabled(False)
-
-            self._next_button.setEnabled(False)
-            self._previous_button.setEnabled(False)
-
-            self._submit_button.show()
-            self._cancel_button.show()
-
-            self._load_button.setEnabled(False)
-            self._save_button.setEnabled(False)
-            self._export_button.setEnabled(False)
-
-        elif self._current_mode == self.NavigationMode:
-            if not self.contacts:
-                self._name_line.clear()
-                self._address_text.clear()
-
-            self._name_line.setReadOnly(True)
-            self._address_text.setReadOnly(True)
-            self._add_button.setEnabled(True)
-
-            number = len(self.contacts)
-            self._edit_button.setEnabled(number >= 1)
-            self._remove_button.setEnabled(number >= 1)
-            self._find_button.setEnabled(number > 2)
-            self._next_button.setEnabled(number > 1)
-            self._previous_button.setEnabled(number > 1)
-
-            self._submit_button.hide()
-            self._cancel_button.hide()
-
-            self._export_button.setEnabled(number >= 1)
-
-            self._load_button.setEnabled(True)
-            self._save_button.setEnabled(number >= 1)
-
-    def save_to_file(self):
-        fileName, _ = QFileDialog.getSaveFileName(self,
-                                                  "Save Address Book", '',
-                                                  "Address Book (*.abk);;All Files (*)")
-
-        if not fileName:
-            return
-
-        try:
-            out_file = open(str(fileName), 'wb')
-        except IOError:
-            QMessageBox.information(self, "Unable to open file",
-                                    f'There was an error opening "{fileName}"')
-            return
-
-        pickle.dump(self.contacts, out_file)
-        out_file.close()
-
-    def load_from_file(self):
-        fileName, _ = QFileDialog.getOpenFileName(self,
-                                                  "Open Address Book", '',
-                                                  "Address Book (*.abk);;All Files (*)")
-
-        if not fileName:
-            return
-
-        try:
-            in_file = open(str(fileName), 'rb')
-        except IOError:
-            QMessageBox.information(self, "Unable to open file",
-                                    f'There was an error opening "{fileName}"')
-            return
-
-        self.contacts = pickle.load(in_file)
-        in_file.close()
-
-        if len(self.contacts) == 0:
-            QMessageBox.information(self, "No contacts in file",
-                                    "The file you are attempting to open contains no contacts.")
-        else:
-            for name, address in self.contacts:
-                self._name_line.setText(name)
-                self._address_text.setText(address)
-
-        self.update_interface(self.NavigationMode)
-
-    def export_as_vcard(self):
-        name = str(self._name_line.text())
-        address = self._address_text.toPlainText()
-
-        name_list = name.split()
-
-        if len(name_list) > 1:
-            first_name = name_list[0]
-            last_name = name_list[-1]
-        else:
-            first_name = name
-            last_name = ''
-
-        file_name = QFileDialog.getSaveFileName(self, "Export Contact",
-                                                '', "vCard Files (*.vcf);;All Files (*)")[0]
-
-        if not file_name:
-            return
-
-        out_file = QFile(file_name)
-
-        if not out_file.open(QIODevice.OpenModeFlag.WriteOnly):
-            QMessageBox.information(self, "Unable to open file", out_file.errorString())
-            return
-
-        out_s = QTextStream(out_file)
-
-        out_s << 'BEGIN:VCARD' << '\n'
-        out_s << 'VERSION:2.1' << '\n'
-        out_s << 'N:' << last_name << ';' << first_name << '\n'
-        out_s << 'FN:' << ' '.join(name_list) << '\n'
-
-        address.replace(';', '\\;')
-        address.replace('\n', ';')
-        address.replace(',', ' ')
-
-        out_s << 'ADR;HOME:;' << address << '\n'
-        out_s << 'END:VCARD' << '\n'
-
-        QMessageBox.information(self, "Export Successful",
-                                f'"{name}" has been exported as a vCard.')
-
-
-class FindDialog(QDialog):
-    def __init__(self, parent=None):
-        super().__init__(parent)
-
-        find_label = QLabel("Enter the name of a contact:")
-        self._line_edit = QLineEdit()
-
-        self._find_button = QPushButton("&Find")
-        self._find_text = ''
-
-        layout = QHBoxLayout()
-        layout.addWidget(find_label)
-        layout.addWidget(self._line_edit)
-        layout.addWidget(self._find_button)
-
-        self.setLayout(layout)
-        self.setWindowTitle("Find a Contact")
-
-        self._find_button.clicked.connect(self.find_clicked)
-        self._find_button.clicked.connect(self.accept)
-
-    def find_clicked(self):
-        text = self._line_edit.text()
-
-        if not text:
-            QMessageBox.information(self, "Empty Field", "Please enter a name.")
-            return
-
-        self._find_text = text
-        self._line_edit.clear()
-        self.hide()
-
-    def get_find_text(self):
-        return self._find_text
-
-
-if __name__ == '__main__':
-    app = QApplication(sys.argv)
-
-    address_book = AddressBook()
-    address_book.show()
-
-    sys.exit(app.exec())
index 00ac2fc267fd8b9bef86b52fc4d3260c4ec7d157..6ba2d1a198c11e2d1ec9c225ed8b5c04c7038e91 100644 (file)
@@ -9,7 +9,7 @@ import sys
 import math
 
 from PySide6.QtCore import QPoint, QRect, QTimer, Qt, Signal, Slot, qWarning
-from PySide6.QtGui import QColor, QFont, QPainter, QPalette, QRegion
+from PySide6.QtGui import QColor, QFont, QPainter, QPainterStateGuard, QPalette, QRegion
 from PySide6.QtWidgets import (QApplication, QGridLayout, QHBoxLayout,
                                QLCDNumber, QPushButton, QSlider,
                                QVBoxLayout, QWidget)
@@ -138,12 +138,11 @@ class CannonField(QWidget):
         painter.setPen(Qt.PenStyle.NoPen)
         painter.setBrush(Qt.GlobalColor.blue)
 
-        painter.save()
-        painter.translate(0, self.height())
-        painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
-        painter.rotate(-self._current_angle)
-        painter.drawRect(CannonField.barrel_rect)
-        painter.restore()
+        with QPainterStateGuard(painter):
+            painter.translate(0, self.height())
+            painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
+            painter.rotate(-self._current_angle)
+            painter.drawRect(CannonField.barrel_rect)
 
     def cannon_rect(self):
         result = QRect(0, 0, 50, 50)
index 4960797bcddc791cb5042a29148aa4544fbd2ddf..c503f9d5e214f6b05a33605b96309a9ebfc5cc77 100644 (file)
@@ -10,7 +10,7 @@ import math
 import random
 
 from PySide6.QtCore import QPoint, QRect, QTime, QTimer, Qt, Signal, Slot, qWarning
-from PySide6.QtGui import QColor, QFont, QPainter, QPalette, QRegion
+from PySide6.QtGui import QColor, QFont, QPainter, QPainterStateGuard, QPalette, QRegion
 from PySide6.QtWidgets import (QApplication, QGridLayout, QHBoxLayout,
                                QLabel, QLCDNumber, QPushButton, QSlider,
                                QVBoxLayout, QWidget)
@@ -184,12 +184,11 @@ class CannonField(QWidget):
         painter.setPen(Qt.PenStyle.NoPen)
         painter.setBrush(Qt.GlobalColor.blue)
 
-        painter.save()
-        painter.translate(0, self.height())
-        painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
-        painter.rotate(-self._current_angle)
-        painter.drawRect(CannonField.barrel_rect)
-        painter.restore()
+        with QPainterStateGuard(painter):
+            painter.translate(0, self.height())
+            painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
+            painter.rotate(-self._current_angle)
+            painter.drawRect(CannonField.barrel_rect)
 
     def cannon_rect(self):
         result = QRect(0, 0, 50, 50)
index c9b8bd5d723c2636937f14eb7ec8974655a56798..39912fd19fba84646d9f86ac34d2afdd9ce585a5 100644 (file)
@@ -11,7 +11,7 @@ import random
 
 from PySide6.QtCore import (QPoint, QRect, QTime, QTimer, Qt,
                             Signal, Slot, qWarning)
-from PySide6.QtGui import QColor, QFont, QPainter, QPalette, QRegion
+from PySide6.QtGui import QColor, QFont, QPainter, QPainterStateGuard, QPalette, QRegion
 from PySide6.QtWidgets import (QApplication, QGridLayout, QHBoxLayout, QLabel,
                                QLCDNumber, QPushButton, QSizePolicy, QSlider,
                                QVBoxLayout, QWidget)
@@ -211,12 +211,11 @@ class CannonField(QWidget):
         painter.setPen(Qt.PenStyle.NoPen)
         painter.setBrush(Qt.GlobalColor.blue)
 
-        painter.save()
-        painter.translate(0, self.height())
-        painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
-        painter.rotate(-self._current_angle)
-        painter.drawRect(CannonField.barrel_rect)
-        painter.restore()
+        with QPainterStateGuard(painter):
+            painter.translate(0, self.height())
+            painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
+            painter.rotate(-self._current_angle)
+            painter.drawRect(CannonField.barrel_rect)
 
     def cannon_rect(self):
         result = QRect(0, 0, 50, 50)
index 00674189b7baedbc02d2ad1d359d6d2119756fa7..d78243489cdc5163abbc0ed24d9dba470b88cbb9 100644 (file)
@@ -11,8 +11,8 @@ import random
 
 from PySide6.QtCore import (QPoint, QRect, QTime, QTimer, QSize, Qt,
                             Signal, Slot, qWarning)
-from PySide6.QtGui import (QColor, QFont, QKeySequence, QPainter, QPalette,
-                           QShortcut, QRegion, QTransform)
+from PySide6.QtGui import (QColor, QFont, QKeySequence, QPainter, QPainterStateGuard,
+                           QPalette, QShortcut, QRegion, QTransform)
 from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QHBoxLayout,
                                QLabel, QLCDNumber, QPushButton, QSizePolicy,
                                QSlider, QVBoxLayout, QWidget)
@@ -241,12 +241,11 @@ class CannonField(QWidget):
         painter.setPen(Qt.PenStyle.NoPen)
         painter.setBrush(Qt.GlobalColor.blue)
 
-        painter.save()
-        painter.translate(0, self.height())
-        painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
-        painter.rotate(-self._current_angle)
-        painter.drawRect(CannonField.barrel_rect)
-        painter.restore()
+        with QPainterStateGuard(painter):
+            painter.translate(0, self.height())
+            painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
+            painter.rotate(-self._current_angle)
+            painter.drawRect(CannonField.barrel_rect)
 
     def cannon_rect(self):
         result = QRect(0, 0, 50, 50)
index f5afdeef82dec7fac3e0ae1e1d86abaa1274fcd7..4f778acbd1c4a093d1448c5e3f66d1af8c74a347 100644 (file)
@@ -7,7 +7,7 @@ from __future__ import annotations
 
 import sys
 
-from PySide6.QtCore import QDir, QFile, Qt, QTextStream
+from PySide6.QtCore import QDir, QFile, QObject, Qt, QTextStream
 from PySide6.QtGui import QAction, QIcon, QKeySequence
 from PySide6.QtWidgets import (QApplication, QFileDialog, QHeaderView,
                                QMainWindow, QMessageBox, QStyle, QTreeWidget,
@@ -93,6 +93,7 @@ class XbelTree(QTreeWidget):
     def __init__(self, parent=None):
         super().__init__(parent)
 
+        self._update_conn_id = None
         self.header().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
         self.setHeaderLabels(("Title", "Location"))
 
@@ -131,17 +132,15 @@ class XbelTree(QTreeWidget):
         self.clear()
 
         # It might not be connected.
-        try:
-            self.itemChanged.disconnect(self.update_dom_element)
-        except RuntimeError:
-            pass
+        if self._update_conn_id:
+            QObject.disconnect(self._update_conn_id)
 
         child = root.firstChildElement('folder')
         while not child.isNull():
             self.parse_folder_element(child)
             child = child.nextSiblingElement('folder')
 
-        self.itemChanged.connect(self.update_dom_element)
+        self._update_conn_id = self.itemChanged.connect(self.update_dom_element)
 
         return True
 
index 5ee2bac0d876d7c713a7e4232896920b19e56be6..a5dabc834ef8b903d013fa76152e4cd106c9aa9f 100644 (file)
@@ -1,5 +1,5 @@
-pip>=24.2
-setuptools==72.1.0
+pip>=25
+setuptools==78.1.0
 importlib_metadata>=6
 importlib_resources>=5.10.2
 packaging>=24
index 9769977f1334f18beed2ea7c1b91abd48ebdbe7e..feb5a01aa82ed8d4a27ed4b809572291c8c52909 100644 (file)
@@ -1,11 +1,11 @@
 # Build dependencies
-setuptools==72.1.0
-packaging==24.1
-build==1.2.1
+setuptools==78.1.0
+packaging==24.2
+build==1.2.2.post1
 wheel==0.43.0
 distro==1.9.0; sys_platform == 'linux'
 patchelf==0.17.2; sys_platform == 'linux'
 # 2.0.2 is the last version that supports Python 3.9
 numpy<=2.0.2; python_version <= '3.9'
 numpy==2.1.3; python_version > '3.9'
-mypy[faster-cache]>=1.14.0
+mypy>=1.15.0    # note: 3.13 with disable-gil is not compiled yet
index c26c8c627db4c03d4dc73585a6193ccec95b8a3d..b437b6daf1d13213729471a763b9d81530456e94 100644 (file)
@@ -26,7 +26,6 @@ from __future__ import annotations
         deployment platform etc.
 
         Note: This file is used by both pyside6-deploy and pyside6-android-deploy
-
 """
 
 import sys
@@ -64,26 +63,27 @@ HELP_MODE = dedent("""
 def main(main_file: Path = None, name: str = None, config_file: Path = None, init: bool = False,
          loglevel=logging.WARNING, dry_run: bool = False, keep_deployment_files: bool = False,
          force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None,
-         mode: str = None):
+         mode: str = None) -> str | None:
+    """
+    Entry point for pyside6-deploy command.
+
+    :return: If successful, the Nuitka command that was executed. None otherwise.
+    """
 
     logging.basicConfig(level=loglevel)
 
-    # in case pyside6-deploy is run from a completely different location than the project
-    # directory
+    # In case pyside6-deploy is run from a completely different location than the project directory
     if main_file and main_file.exists():
         config_file = main_file.parent / "pysidedeploy.spec"
 
     if config_file and not config_file.exists() and not main_file.exists():
         raise RuntimeError(dedent("""
             Directory does not contain main.py file.
-            Please specify the main python entrypoint file or the config file.
-            Run "pyside6-deploy desktop --help" to see info about cli options.
+            Please specify the main Python entry point file or the pysidedeploy.spec config file.
+            Run "pyside6-deploy --help" to see info about CLI options.
 
             pyside6-deploy exiting..."""))
 
-    # Nuitka command to run
-    command_str = None
-    config = None
     logging.info("[DEPLOY] Start")
 
     if extra_ignore_dirs:
@@ -104,7 +104,7 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini
     if config_file_exists:
         logging.info(f"[DEPLOY] Using existing config file {config_file}")
     else:
-        config_file = create_config_file(main_file=main_file, dry_run=dry_run)
+        config_file = create_config_file(main_file=main_file, dry_run=dry_run)
 
     config = DesktopConfig(config_file=config_file, source_file=main_file, python_exe=python.exe,
                            dry_run=dry_run, existing_config_file=config_file_exists,
@@ -130,7 +130,7 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini
                      f"{[str(qml_file) for qml_file in config.qml_files]}")
 
     if init:
-        # config file created above. Exiting.
+        # Config file created above. Exiting.
         logging.info(f"[DEPLOY]: Config file {config.config_file} created")
         return
 
@@ -143,8 +143,9 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini
         print("[DEPLOY] QtSql Application is not supported on macOS with pyside6-deploy")
         return
 
+    command_str = None
     try:
-        # create executable
+        # Run the Nuitka command to create the executable
         if not dry_run:
             logging.info("[DEPLOY] Deploying application")
 
@@ -158,6 +159,8 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini
                                                dry_run=dry_run,
                                                permissions=config.permissions,
                                                mode=config.mode)
+        if not dry_run:
+            logging.info("[DEPLOY] Successfully deployed application")
     except Exception:
         print(f"[DEPLOY] Exception occurred: {traceback.format_exc()}")
     finally:
index 45dd874f5a7c43e95a3979ad3b57ea5a818da608..d70e38ea2ef1bfca089ac9ea43d04c011bf908dd 100644 (file)
@@ -17,8 +17,8 @@ from tqdm import tqdm
 
 # the tag number does not matter much since we update the sdk later
 DEFAULT_SDK_TAG = 6514223
-ANDROID_NDK_VERSION = "26b"
-ANDROID_NDK_VERSION_NUMBER_SUFFIX = "10909125"
+ANDROID_NDK_VERSION = "27c"
+ANDROID_NDK_VERSION_NUMBER_SUFFIX = "12479018"
 
 
 def run_command(command: list[str], cwd: str | None = None, ignore_fail: bool = False,
@@ -163,16 +163,9 @@ def download_android_ndk(ndk_path: Path):
 
             print("Unpacking Android Ndk")
             if sys.platform == "darwin":
-                extract_dmg(file=(ndk_path
-                            / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"),
-                            destination=ndk_path)
-                ndk_version_path = (ndk_version_path
-                                    / (f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app"
-                                       "/Contents/NDK"))
+                extract_dmg(file=ndk_zip_path, destination=ndk_path)
             else:
-                extract_zip(file=(ndk_path
-                            / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"),
-                            destination=ndk_path)
+                extract_zip(file=ndk_zip_path, destination=ndk_path)
         except Exception as e:
             print(f"Error occurred while downloading and unpacking Android NDK: {e}")
             if ndk_path.exists():
index 998a175cecefd6d487f89def8cd2fe5e39fa1f67..853f5f6a213f4843da3aa1a63cd956d5f349bd3c 100644 (file)
@@ -11,7 +11,7 @@ from configparser import ConfigParser
 from pathlib import Path
 from enum import Enum
 
-from project_lib import ProjectData, DesignStudioProject
+from project_lib import ProjectData, DesignStudioProject, resolve_valid_project_file
 from . import (DEFAULT_APP_ICON, DEFAULT_IGNORE_DIRS, find_pyside_modules,
                find_permission_categories, QtDependencyReader, run_qmlimportscanner)
 
@@ -106,24 +106,21 @@ class Config(BaseConfig):
         self.extra_ignore_dirs = extra_ignore_dirs
         self._dry_run = dry_run
         self.qml_modules = set()
-        # set source_file
+
         self.source_file = Path(
-            self.set_or_fetch(config_property_val=source_file, config_property_key="input_file")
+            self.set_or_fetch(property_value=source_file, property_key="input_file")
         ).resolve()
 
-        # set python path
         self.python_path = Path(
             self.set_or_fetch(
-                config_property_val=python_exe,
-                config_property_key="python_path",
-                config_property_group="python",
+                property_value=python_exe,
+                property_key="python_path",
+                property_group="python",
             )
         )
 
-        # set application name
-        self.title = self.set_or_fetch(config_property_val=name, config_property_key="title")
+        self.title = self.set_or_fetch(property_value=name, property_key="title")
 
-        # set application icon
         config_icon = self.get_value("app", "icon")
         if config_icon:
             self._icon = str(Path(config_icon).resolve())
@@ -176,96 +173,107 @@ class Config(BaseConfig):
 
         self.modules = []
 
-    def set_or_fetch(self, config_property_val, config_property_key, config_property_group="app"):
+    def set_or_fetch(self, property_value, property_key, property_group="app") -> str:
         """
-        Set the configuration value if provided, otherwise fetch the existing value.
-        Raise an exception if neither is available.
+        If a new property value is provided, store it in the config file
+        Otherwise return the existing value in the config file.
+        Raise an exception if neither are available.
 
-        :param value: The value to set if provided.
-        :param key: The configuration key.
-        :param group: The configuration group (default is "app").
+        :param property_value: The value to set if provided.
+        :param property_key: The configuration key.
+        :param property_group: The configuration group (default is "app").
         :return: The configuration value.
         :raises RuntimeError: If no value is provided and no existing value is found.
         """
-        existing_value = self.get_value(config_property_group, config_property_key)
+        existing_value = self.get_value(property_group, property_key)
 
-        if config_property_val:
-            self.set_value(config_property_group, config_property_key, str(config_property_val))
-            return config_property_val
-        elif existing_value:
+        if property_value:
+            self.set_value(property_group, property_key, str(property_value))
+            return property_value
+        if existing_value:
             return existing_value
-        else:
-            raise RuntimeError(
-                f"[DEPLOY] No value for {config_property_key} specified in config file or as cli"
-                " option"
-            )
+
+        raise RuntimeError(
+            f"[DEPLOY] No value for {property_key} specified in config file or as cli option"
+        )
 
     @property
-    def dry_run(self):
+    def dry_run(self) -> bool:
         return self._dry_run
 
     @property
-    def generated_files_path(self):
+    def generated_files_path(self) -> Path:
         return self._generated_files_path
 
     @property
-    def qml_files(self):
+    def qml_files(self) -> list[Path]:
         return self._qml_files
 
     @qml_files.setter
-    def qml_files(self, qml_files):
+    def qml_files(self, qml_files: list[Path]):
         self._qml_files = qml_files
         qml_files = [str(file.absolute().relative_to(self.project_dir.absolute()))
                      if file.absolute().is_relative_to(self.project_dir) else str(file.absolute())
                      for file in self.qml_files]
+        qml_files.sort()
         self.set_value("qt", "qml_files", ",".join(qml_files))
 
     @property
-    def project_dir(self):
+    def project_dir(self) -> Path:
         return self._project_dir
 
     @project_dir.setter
-    def project_dir(self, project_dir):
+    def project_dir(self, project_dir: Path) -> None:
+        rel_path = (
+            project_dir.relative_to(self.config_file.parent)
+            if project_dir.is_relative_to(self.config_file.parent)
+            else project_dir
+        )
         self._project_dir = project_dir
-        self.set_value("app", "project_dir", str(project_dir))
+        self.set_value("app", "project_dir", str(rel_path))
 
     @property
-    def project_file(self):
+    def project_file(self) -> Path:
         return self._project_file
 
     @project_file.setter
-    def project_file(self, project_file):
+    def project_file(self, project_file: Path):
         self._project_file = project_file
         self.set_value("app", "project_file", str(project_file.relative_to(self.project_dir)))
 
     @property
-    def title(self):
+    def title(self) -> str:
         return self._title
 
     @title.setter
-    def title(self, title):
+    def title(self, title: str):
         self._title = title
 
     @property
-    def icon(self):
+    def icon(self) -> str:
         return self._icon
 
     @icon.setter
-    def icon(self, icon):
+    def icon(self, icon: str):
         self._icon = icon
         self.set_value("app", "icon", icon)
 
     @property
-    def source_file(self):
+    def source_file(self) -> Path:
         return self._source_file
 
     @source_file.setter
-    def source_file(self, source_file: Path):
+    def source_file(self, source_file: Path) -> None:
+        rel_path = (
+            source_file.relative_to(self.config_file.parent)
+            if source_file.is_relative_to(self.config_file.parent)
+            else source_file
+        )
         self._source_file = source_file
-        self.set_value("app", "input_file", str(source_file))
+        self.set_value("app", "input_file", str(rel_path))
 
     @property
-    def python_path(self):
+    def python_path(self) -> Path:
         return self._python_path
 
     @python_path.setter
@@ -273,25 +281,26 @@ class Config(BaseConfig):
         self._python_path = python_path
 
     @property
-    def extra_args(self):
+    def extra_args(self) -> str:
         return self.get_value("nuitka", "extra_args")
 
     @extra_args.setter
-    def extra_args(self, extra_args):
+    def extra_args(self, extra_args: str):
         self.set_value("nuitka", "extra_args", extra_args)
 
     @property
-    def excluded_qml_plugins(self):
+    def excluded_qml_plugins(self) -> list[str]:
         return self._excluded_qml_plugins
 
     @excluded_qml_plugins.setter
-    def excluded_qml_plugins(self, excluded_qml_plugins):
+    def excluded_qml_plugins(self, excluded_qml_plugins: list[str]):
         self._excluded_qml_plugins = excluded_qml_plugins
         if excluded_qml_plugins:  # check required for Android
+            excluded_qml_plugins.sort()
             self.set_value("qt", "excluded_qml_plugins", ",".join(excluded_qml_plugins))
 
     @property
-    def exe_dir(self):
+    def exe_dir(self) -> Path:
         return self._exe_dir
 
     @exe_dir.setter
@@ -300,12 +309,13 @@ class Config(BaseConfig):
         self.set_value("app", "exec_directory", str(exe_dir))
 
     @property
-    def modules(self):
+    def modules(self) -> list[str]:
         return self._modules
 
     @modules.setter
-    def modules(self, modules):
+    def modules(self, modules: list[str]):
         self._modules = modules
+        modules.sort()
         self.set_value("qt", "modules", ",".join(modules))
 
     def _find_qml_files(self):
@@ -314,7 +324,6 @@ class Config(BaseConfig):
         field qml_files is empty in the config_file
         """
 
-        qml_files = []
         if self.project_data:
             qml_files = [(self.project_dir / str(qml_file)) for qml_file in
                          self.project_data.qml_files]
@@ -340,7 +349,7 @@ class Config(BaseConfig):
         if DesignStudioProject.is_ds_project(self.source_file):
             return DesignStudioProject(self.source_file).project_dir
 
-        # there is no other way to find the project_dir than assume it is the parent directory
+        # There is no other way to find the project_dir than assume it is the parent directory
         # of source_file
         return self.source_file.parent
 
@@ -353,15 +362,12 @@ class Config(BaseConfig):
         else:
             pyproject_location = self.project_dir
 
-        files = list(pyproject_location.glob("*.pyproject"))
-        if not files:
-            logging.info("[DEPLOY] No .pyproject file found. Project file not set")
-            return None
-        if len(files) > 1:
-            warnings.warn("DEPLOY: More that one .pyproject files found. Project file not set")
-            return None
-
-        return files[0]
+        try:
+            return resolve_valid_project_file(pyproject_location)
+        except ValueError as e:
+            logging.warning(f"[DEPLOY] Unable to resolve a valid project file. Proceeding without a"
+                            f" project file. Details:\n{e}.")
+        return None
 
     def _find_excluded_qml_plugins(self) -> list[str] | None:
         if not self.qml_files and not DesignStudioProject.is_ds_project(self.source_file):
@@ -459,25 +465,27 @@ class DesktopConfig(Config):
                                    f"the resources manually using pyside6-rcc")
 
     @property
-    def qt_plugins(self):
+    def qt_plugins(self) -> list[str]:
         return self._qt_plugins
 
     @qt_plugins.setter
-    def qt_plugins(self, qt_plugins):
+    def qt_plugins(self, qt_plugins: list[str]):
         self._qt_plugins = qt_plugins
+        qt_plugins.sort()
         self.set_value("qt", "plugins", ",".join(qt_plugins))
 
     @property
-    def permissions(self):
+    def permissions(self) -> list[str]:
         return self._permissions
 
     @permissions.setter
-    def permissions(self, permissions):
+    def permissions(self, permissions: list[str]):
         self._permissions = permissions
+        permissions.sort()
         self.set_value("nuitka", "macos.permissions", ",".join(permissions))
 
     @property
-    def mode(self):
+    def mode(self) -> NuitkaMode:
         return self._mode
 
     @mode.setter
@@ -516,7 +524,7 @@ class DesktopConfig(Config):
         logging.info(f"[DEPLOY] Usage descriptions for the {perm_categories_str} will be added to "
                      "the Info.plist file of the macOS application bundle")
 
-        # handling permissions
+        # Handling permissions
         for perm_category in perm_categories:
             if perm_category in PERMISSION_MAP:
                 permissions.append(PERMISSION_MAP[perm_category])
index 618a0294d5eebd4ff2e17ef437399404aecae6d8..5e33d5399fc731ca1e448e80ad35e35ce3e6475e 100644 (file)
@@ -3,17 +3,16 @@
 # Title of your application
 title = pyside_app_demo
 
-# Project Directory. The general assumption is that project_dir is the parent directory
-# of input_file
+# Project root directory. Default: The parent directory of input_file
 project_dir =
 
-# Source file path
+# Source file entry point path. Default: main.py
 input_file =
 
 # Directory where the executable output is generated
 exec_directory =
 
-# Path to .pyproject project file
+# Path to the project file relative to project_dir
 project_file =
 
 # Application icon
@@ -24,76 +23,76 @@ icon =
 # Python path
 python_path =
 
-# python packages to install
-packages = Nuitka==2.5.1
+# Python packages to install
+packages = Nuitka==2.7.11
 
-# buildozer: for deploying Android application
+# Buildozer: for deploying Android application
 android_packages = buildozer==1.5.0,cython==0.29.33
 
 [qt]
 
-# Comma separated path to QML files required
-# normally all the QML files required by the project are added automatically
+# Paths to required QML files. Comma separated
+# Normally all the QML files required by the project are added automatically
+# Design Studio projects include the QML files using Qt resources
 qml_files =
 
-# excluded qml plugin binaries
+# Excluded qml plugin binaries
 excluded_qml_plugins =
 
 # Qt modules used. Comma separated
 modules =
 
-# Qt plugins used by the application. Only relevant for desktop deployment. For Qt plugins used
-# in Android application see [android][plugins]
+# Qt plugins used by the application. Only relevant for desktop deployment
+# For Qt plugins used in Android application see [android][plugins]
 plugins =
 
 [android]
 
-# path to PySide wheel
+# Path to PySide wheel
 wheel_pyside =
 
-# path to Shiboken wheel
+# Path to Shiboken wheel
 wheel_shiboken =
 
-# plugins to be copied to libs folder of the packaged application. Comma separated
+# Plugins to be copied to libs folder of the packaged application. Comma separated
 plugins =
 
 [nuitka]
 
-# usage description for permissions requested by the app as found in the Info.plist file
-# of the app bundle
+# Usage description for permissions requested by the app as found in the Info.plist file
+# of the app bundle. Comma separated
 # eg: NSCameraUsageDescription:CameraAccess
 macos.permissions =
 
-# mode of using Nuitka. Accepts standalone or onefile. Default is onefile.
+# Mode of using Nuitka. Accepts standalone or onefile. Default: onefile
 mode = onefile
 
-# (str) specify any extra nuitka arguments
+# Specify any extra nuitka arguments
 # eg: extra_args = --show-modules --follow-stdlib
 extra_args = --quiet --noinclude-qt-translations
 
 [buildozer]
 
-# build mode
-# possible options: [release, debug]
-# release creates an aab, while debug creates an apk
+# Build mode
+# Possible values: [release, debug]
+# Release creates a .aab, while debug creates a .apk
 mode = debug
 
-# contrains path to PySide6 and shiboken6 recipe dir
+# Path to PySide6 and shiboken6 recipe dir
 recipe_dir =
 
-# path to extra Qt Android jars to be loaded by the application
+# Path to extra Qt Android .jar files to be loaded by the application
 jars_dir =
 
-# if empty uses default ndk path downloaded by buildozer
+# If empty, uses default NDK path downloaded by buildozer
 ndk_path =
 
-# if empty uses default sdk path downloaded by buildozer
+# If empty, uses default SDK path downloaded by buildozer
 sdk_path =
 
-# other libraries to be loaded. Comma separated.
-# loaded at app startup
+# Other libraries to be loaded at app startup. Comma separated.
 local_libs =
 
-# architecture of deployed platform
-# possible values: ["aarch64", "armv7a", "i686", "x86_64"]
+# Architecture of deployed platform
+# Possible values: ["aarch64", "armv7a", "i686", "x86_64"]
 arch =
index 6bdfb20b7ab821dea910a4e52581ce02a3bf18c3..63b40060a3f47718202ada5465fdcb6c63b9d571 100644 (file)
@@ -30,15 +30,25 @@ def get_py_files(project_dir: Path, extra_ignore_dirs: tuple[Path] = None, proje
         qrc_candidates = project_data.qrc_files
 
         def add_uic_qrc_candidates(candidates, candidate_type):
-            possible_py_candidates = [(file.parent / f"{candidate_type}_{file.stem}.py")
-                                      for file in candidates
-                                      if (file.parent / f"{candidate_type}_{file.stem}.py").exists()
-                                      ]
-
-            if len(possible_py_candidates) != len(candidates):
-                warnings.warn(f"[DEPLOY] The number of {candidate_type} files and their "
-                              "corresponding Python files don't match.",
-                              category=RuntimeWarning)
+            possible_py_candidates = []
+            missing_files = []
+            for file in candidates:
+                py_file = file.parent / f"{candidate_type}_{file.stem}.py"
+                if py_file.exists():
+                    possible_py_candidates.append(py_file)
+                else:
+                    missing_files.append((str(file), str(py_file)))
+
+            if missing_files:
+                missing_details = "\n".join(
+                    f"{candidate_type.upper()} file: {src} -> Missing Python file: {dst}"
+                    for src, dst in missing_files
+                )
+                warnings.warn(
+                    f"[DEPLOY] The following {candidate_type} files do not have corresponding "
+                    f"Python files:\n {missing_details}",
+                    category=RuntimeWarning
+                )
 
             py_candidates.extend(possible_py_candidates)
 
@@ -46,7 +56,7 @@ def get_py_files(project_dir: Path, extra_ignore_dirs: tuple[Path] = None, proje
             add_uic_qrc_candidates(ui_candidates, "ui")
 
         if qrc_candidates:
-            add_uic_qrc_candidates(qrc_candidates, "qrc")
+            add_uic_qrc_candidates(qrc_candidates, "rc")
 
         return py_candidates
 
index d14c3334aa6c7f24983c57f8aa56f9c2f17e53fe..f3c0c5606252a94e0f83cba9f4d7bfc1db587986 100644 (file)
@@ -70,9 +70,11 @@ def _attribute(node: ast.Attribute) -> tuple[str, str]:
     return node.value.id, node.attr
 
 
-def _name(node: ast.Name | ast.Attribute) -> str:
+def _name(node: ast.Name | ast.Attribute | ast.Constant) -> str:
     """Return the name of something that is either an attribute or a name,
        such as base classes or call.func"""
+    if isinstance(node, ast.Constant):
+        return str(node.value)
     if isinstance(node, ast.Attribute):
         qualifier, name = _attribute(node)
         return f"{qualifier}.{node.attr}"
index 1af51e07d10316128eac7c46c10e8ab698a85869..762e76f3163636a7dd71af02e82115d53378920d 100644 (file)
@@ -2,46 +2,36 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 from __future__ import annotations
 
-"""
-Builds a '.pyproject' file
-
-Builds Qt Designer forms, resource files and QML type files
-
-Deploys the application by creating an executable for the corresponding platform
-
-For each entry in a '.pyproject' file:
-- <name>.pyproject: Recurse to handle subproject
-- <name>.qrc      : Runs the resource compiler to create a file rc_<name>.py
-- <name>.ui       : Runs the user interface compiler to create a file ui_<name>.py
-
-For a Python file declaring a QML module, a directory matching the URI is
-created and populated with .qmltypes and qmldir files for use by code analysis
-tools. Currently, only one QML module consisting of several classes can be
-handled per project file.
-"""
 import sys
 import os
 from pathlib import Path
 from argparse import ArgumentParser, RawTextHelpFormatter
 
-from project_lib import (QmlProjectData, check_qml_decorators, is_python_file,
-                         QMLDIR_FILE, MOD_CMD, METATYPES_JSON_SUFFIX,
-                         SHADER_SUFFIXES, TRANSLATION_SUFFIX,
-                         requires_rebuild, run_command, remove_path,
-                         ProjectData, resolve_project_file, new_project,
-                         ProjectType, ClOptions, DesignStudioProject)
-
-MODE_HELP = """build    Builds the project
-run        Builds the project and runs the first file")
-clean      Cleans the build artifacts")
-qmllint    Runs the qmllint tool
-deploy     Deploys the application
-lupdate    Updates translation (.ts) files
-new-ui     Creates a new QtWidgets project with a Qt Designer-based main window
-new-widget Creates a new QtWidgets project with a main window
-new-quick  Creates a new QtQuick project
+from project_lib import (QmlProjectData, check_qml_decorators, is_python_file, migrate_pyproject,
+                         QMLDIR_FILE, MOD_CMD, METATYPES_JSON_SUFFIX, SHADER_SUFFIXES,
+                         TRANSLATION_SUFFIX, requires_rebuild, run_command, remove_path,
+                         ProjectData, resolve_valid_project_file, new_project, NewProjectTypes,
+                         ClOptions, DesignStudioProject)
+
+DESCRIPTION = """
+pyside6-project is a command line tool for creating, building and deploying Qt for Python
+applications. It operates on project files which are also used by Qt Creator.
+
+Official documentation:
+https://doc.qt.io/qtforpython-6/tools/pyside-project.html
 """
 
+OPERATION_HELP = {
+    "build": "Build the project. Compiles resources, UI files, and QML files if existing and "
+             "necessary.",
+    "run": "Build and run the project.",
+    "clean": "Clean build artifacts and generated files from the project directory.",
+    "qmllint": "Run the qmllint tool on QML files in the project.",
+    "deploy": "Create a deployable package of the application including all dependencies.",
+    "lupdate": "Update translation files (.ts) with new strings from source files.",
+    "migrate-pyproject": "Migrate a *.pyproject file to pyproject.toml format."
+}
+
 UIC_CMD = "pyside6-uic"
 RCC_CMD = "pyside6-rcc"
 LRELEASE_CMD = "pyside6-lrelease"
@@ -51,10 +41,6 @@ QMLLINT_CMD = "pyside6-qmllint"
 QSB_CMD = "pyside6-qsb"
 DEPLOY_CMD = "pyside6-deploy"
 
-NEW_PROJECT_TYPES = {"new-quick": ProjectType.QUICK,
-                     "new-ui": ProjectType.WIDGET_FORM,
-                     "new-widget": ProjectType.WIDGET}
-
 
 def _sort_sources(files: list[Path]) -> list[Path]:
     """Sort the sources for building, ensure .qrc is last since it might depend
@@ -200,11 +186,11 @@ class Project:
 
         self._regenerate_qmldir()
 
-    def run(self):
+    def run(self) -> int:
         """Runs the project"""
         self.build()
         cmd = [sys.executable, str(self.project.main_file)]
-        run_command(cmd, cwd=self.project.project_file.parent)
+        return run_command(cmd, cwd=self.project.project_file.parent)
 
     def _clean_file(self, source: Path):
         """Clean an artifact."""
@@ -271,33 +257,51 @@ class Project:
         cmd_prefix = [LUPDATE_CMD] + [os.fspath(p.relative_to(project_dir)) for p in source_files]
         cmd_prefix.append("-ts")
         for ts_file in self.project.ts_files:
+            ts_dir = ts_file.parent
+            if not ts_dir.exists():
+                ts_dir.mkdir(parents=True, exist_ok=True)
             if requires_rebuild(source_files, ts_file):
                 cmd = cmd_prefix
-                cmd.append(ts_file.name)
+                cmd.append(os.fspath(ts_file))
                 run_command(cmd, cwd=project_dir)
 
 
-def main(mode: str = None, file: str = None, dry_run: bool = False, quiet: bool = False,
-         force: bool = False, qml_module: bool = None):
+def main(mode: str = None, dry_run: bool = False, quiet: bool = False, force: bool = False,
+         qml_module: bool = None, project_dir: str = None, project_path: str = None,
+         legacy_pyproject: bool = False):
     cl_options = ClOptions(dry_run=dry_run, quiet=quiet,  # noqa: F841
                            force=force, qml_module=qml_module)
 
-    new_project_type = NEW_PROJECT_TYPES.get(mode)
-    if new_project_type:
-        if not file:
-            print(f"{mode} requires a directory name.", file=sys.stderr)
+    if new_project_type := NewProjectTypes.find_by_command(mode):
+        if not project_dir:
+            print(f"Error creating new project: {mode} requires a directory name or path",
+                  file=sys.stderr)
+            sys.exit(1)
+
+        project_dir = Path(project_dir)
+        try:
+            project_dir.resolve()
+            project_dir.mkdir(parents=True, exist_ok=True)
+        except (OSError, RuntimeError, ValueError):
+            print("Invalid project name", file=sys.stderr)
             sys.exit(1)
-        sys.exit(new_project(file, new_project_type))
 
-    project_file = resolve_project_file(file)
-    if not project_file:
-        print(f"Cannot determine project_file {file}", file=sys.stderr)
+        sys.exit(new_project(project_dir, new_project_type, legacy_pyproject))
+
+    if mode == "migrate-pyproject":
+        sys.exit(migrate_pyproject(project_path))
+
+    try:
+        project_file = resolve_valid_project_file(project_path)
+    except ValueError as e:
+        print(f"Error: {e}", file=sys.stderr)
         sys.exit(1)
+
     project = Project(project_file)
     if mode == "build":
         project.build()
     elif mode == "run":
-        project.run()
+        sys.exit(project.run())
     elif mode == "clean":
         project.clean()
     elif mode == "qmllint":
@@ -312,20 +316,33 @@ def main(mode: str = None, file: str = None, dry_run: bool = False, quiet: bool
 
 
 if __name__ == "__main__":
-    parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
+    parser = ArgumentParser(description=DESCRIPTION, formatter_class=RawTextHelpFormatter)
     parser.add_argument("--quiet", "-q", action="store_true", help="Quiet")
     parser.add_argument("--dry-run", "-n", action="store_true", help="Only print commands")
     parser.add_argument("--force", "-f", action="store_true", help="Force rebuild")
     parser.add_argument("--qml-module", "-Q", action="store_true",
                         help="Perform check for QML module")
-    mode_choices = ["build", "run", "clean", "qmllint", "deploy", "lupdate"]
-    mode_choices.extend(NEW_PROJECT_TYPES.keys())
-    parser.add_argument("mode", choices=mode_choices, default="build",
-                        type=str, help=MODE_HELP)
 
-    # TODO: improve the command structure.
-    #  "File" argument is not correct when doing new-... project
-    parser.add_argument("file", help="Project file", nargs="?", type=str)
+    # Create subparsers for the two different command branches
+    subparsers = parser.add_subparsers(dest='mode', required=True)
+
+    # Add subparser for project creation commands
+    for project_type in NewProjectTypes:
+        new_parser = subparsers.add_parser(project_type.value.command,
+                                           help=project_type.value.description)
+        new_parser.add_argument(
+            "project_dir", help="Name or location of the new project", nargs="?", type=str)
+
+        new_parser.add_argument(
+            "--legacy-pyproject", action="store_true", help="Create a legacy *.pyproject file")
+
+    # Add subparser for project operation commands
+    for op_mode, op_help in OPERATION_HELP.items():
+        op_parser = subparsers.add_parser(op_mode, help=op_help)
+        op_parser.add_argument("project_path", nargs="?", type=str, help="Path to the project file")
 
     args = parser.parse_args()
-    main(args.mode, args.file, args.dry_run, args.quiet, args.force, args.qml_module)
+
+    main(args.mode, args.dry_run, args.quiet, args.force, args.qml_module,
+         getattr(args, "project_dir", None), getattr(args, "project_path", None),
+         getattr(args, "legacy_pyproject", None))
index aafaa44bf2c975dea62f2b877a1ed1478b435a5b..3c6aa5024031db68cd612d4183db51a31c6252a5 100644 (file)
@@ -7,7 +7,10 @@ from dataclasses import dataclass
 QTPATHS_CMD = "qtpaths6"
 MOD_CMD = "pyside6-metaobjectdump"
 
-PROJECT_FILE_SUFFIX = ".pyproject"
+PYPROJECT_TOML_PATTERN = "pyproject.toml"
+PYPROJECT_JSON_PATTERN = "*.pyproject"
+# Note that the order is important, as the first pattern that matches is used
+PYPROJECT_FILE_PATTERNS = [PYPROJECT_TOML_PATTERN, PYPROJECT_JSON_PATTERN]
 QMLDIR_FILE = "qmldir"
 
 QML_IMPORT_NAME = "QML_IMPORT_NAME"
@@ -41,8 +44,10 @@ class ClOptions(metaclass=Singleton):
 
 
 from .utils import (run_command, requires_rebuild, remove_path, package_dir, qtpaths,
-                    qt_metatype_json_dir, resolve_project_file)
+                    qt_metatype_json_dir, resolve_valid_project_file)
 from .project_data import (is_python_file, ProjectData, QmlProjectData,
                            check_qml_decorators)
-from .newproject import new_project, ProjectType
+from .newproject import new_project, NewProjectTypes
 from .design_studio_project import DesignStudioProject
+from .pyproject_toml import parse_pyproject_toml, write_pyproject_toml, migrate_pyproject
+from .pyproject_json import parse_pyproject_json
index 80ed5a75e83292d0718afc4f5e208db3eee4faa8..d83318739a0d649c27ff86f2aed1291e7476b488 100644 (file)
@@ -2,23 +2,16 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 from __future__ import annotations
 
-import json
 import os
 import sys
+from dataclasses import dataclass
 from enum import Enum
 from pathlib import Path
 
-"""New project generation code."""
-
-
-Project = list[tuple[str, str]]  # tuple of (filename, contents).
-
-
-class ProjectType(Enum):
-    WIDGET_FORM = 1
-    WIDGET = 2
-    QUICK = 3
+from .pyproject_toml import write_pyproject_toml
+from .pyproject_json import write_pyproject_json
 
+"""New project generation code."""
 
 _WIDGET_MAIN = """if __name__ == '__main__':
     app = QApplication(sys.argv)
@@ -27,23 +20,19 @@ _WIDGET_MAIN = """if __name__ == '__main__':
     sys.exit(app.exec())
 """
 
-
 _WIDGET_IMPORTS = """import sys
 from PySide6.QtWidgets import QApplication, QMainWindow
 """
 
-
 _WIDGET_CLASS_DEFINITION = """class MainWindow(QMainWindow):
     def __init__(self):
         super().__init__()
 """
 
-
 _WIDGET_SETUP_UI_CODE = """        self._ui = Ui_MainWindow()
         self._ui.setupUi(self)
 """
 
-
 _MAINWINDOW_FORM = """<?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
  <class>MainWindow</class>
@@ -75,7 +64,6 @@ _MAINWINDOW_FORM = """<?xml version="1.0" encoding="UTF-8"?>
 </ui>
 """
 
-
 _QUICK_FORM = """import QtQuick
 import QtQuick.Controls
 
@@ -107,28 +95,46 @@ if __name__ == "__main__":
     sys.exit(exit_code)
 """
 
+NewProjectFiles = list[tuple[str, str]]  # tuple of (filename, contents).
+
 
-def _write_project(directory: Path, files: Project):
-    """Write out the project."""
-    file_list = []
-    for file, contents in files:
-        (directory / file).write_text(contents)
-        print(f"Wrote {directory.name}{os.sep}{file}.")
-        file_list.append(file)
-    pyproject = {"files": file_list}
-    pyproject_file = f"{directory}.pyproject"
-    (directory / pyproject_file).write_text(json.dumps(pyproject))
-    print(f"Wrote {directory.name}{os.sep}{pyproject_file}.")
+@dataclass(frozen=True)
+class NewProjectType:
+    command: str
+    description: str
+    files: NewProjectFiles
 
 
-def _widget_project() -> Project:
+def _write_project(directory: Path, files: NewProjectFiles, legacy_pyproject: bool):
+    """
+    Create the project files in the specified directory.
+
+    :param directory: The directory to create the project in.
+    :param files: The files that belong to the project to create.
+    """
+    file_names = []
+    for file_name, contents in files:
+        (directory / file_name).write_text(contents)
+        print(f"Wrote {directory.name}{os.sep}{file_name}.")
+        file_names.append(file_name)
+
+    if legacy_pyproject:
+        pyproject_file = directory / f"{directory.name}.pyproject"
+        write_pyproject_json(pyproject_file, file_names)
+    else:
+        pyproject_file = directory / "pyproject.toml"
+        write_pyproject_toml(pyproject_file, directory.name, file_names)
+    print(f"Wrote {pyproject_file}.")
+
+
+def _widget_project() -> NewProjectFiles:
     """Create a (form-less) widgets project."""
     main_py = (_WIDGET_IMPORTS + "\n\n" + _WIDGET_CLASS_DEFINITION + "\n\n"
                + _WIDGET_MAIN)
     return [("main.py", main_py)]
 
 
-def _ui_form_project() -> Project:
+def _ui_form_project() -> NewProjectFiles:
     """Create a Qt Designer .ui form based widgets project."""
     main_py = (_WIDGET_IMPORTS
                + "\nfrom ui_mainwindow import Ui_MainWindow\n\n\n"
@@ -138,28 +144,46 @@ def _ui_form_project() -> Project:
             ("mainwindow.ui", _MAINWINDOW_FORM)]
 
 
-def _qml_project() -> Project:
+def _qml_project() -> NewProjectFiles:
     """Create a QML project."""
     return [("main.py", _QUICK_MAIN),
             ("main.qml", _QUICK_FORM)]
 
 
-def new_project(directory_s: str,
-                project_type: ProjectType = ProjectType.WIDGET_FORM) -> int:
-    directory = Path(directory_s)
-    if directory.exists():
-        print(f"{directory_s} already exists.", file=sys.stderr)
-        return -1
-    directory.mkdir(parents=True)
+class NewProjectTypes(Enum):
+    QUICK = NewProjectType("new-quick", "Create a new Qt Quick project", _qml_project())
+    WIDGET_FORM = NewProjectType("new-ui", "Create a new Qt Widgets Form project",
+                                 _ui_form_project())
+    WIDGET = NewProjectType("new-widget", "Create a new Qt Widgets project", _widget_project())
 
-    if project_type == ProjectType.WIDGET_FORM:
-        project = _ui_form_project()
-    elif project_type == ProjectType.QUICK:
-        project = _qml_project()
-    else:
-        project = _widget_project()
-    _write_project(directory, project)
-    if project_type == ProjectType.WIDGET_FORM:
-        print(f'Run "pyside6-project build {directory_s}" to build the project')
-    print(f'Run "python {directory.name}{os.sep}main.py" to run the project')
+    @staticmethod
+    def find_by_command(command: str) -> NewProjectType | None:
+        return next((pt.value for pt in NewProjectTypes if pt.value.command == command), None)
+
+
+def new_project(
+    project_dir: Path, project_type: NewProjectType, legacy_pyproject: bool
+) -> int:
+    """
+    Create a new project at the specified project_dir directory.
+
+    :param project_dir: The directory path to create the project. If existing, must be empty.
+    :param project_type: The Qt type of project to create (Qt Widgets, Qt Quick, etc.)
+
+    :return: 0 if the project was created successfully, otherwise 1.
+    """
+    if any(project_dir.iterdir()):
+        print(f"Can not create project at {project_dir}: directory is not empty.", file=sys.stderr)
+        return 1
+    project_dir.mkdir(parents=True, exist_ok=True)
+
+    try:
+        _write_project(project_dir, project_type.files, legacy_pyproject)
+    except Exception as e:
+        print(f"Error creating project file: {str(e)}", file=sys.stderr)
+        return 1
+
+    if project_type == NewProjectTypes.WIDGET_FORM:
+        print(f'Run "pyside6-project build {project_dir}" to build the project')
+    print(f'Run "pyside6-project run {project_dir / "main.py"}" to run the project')
     return 0
index 445f02b8146955f9c3e8de3fb7e8599fbe9aa257..9a219c957a9f4deed17a03409185f488d18be077 100644 (file)
@@ -7,9 +7,11 @@ import os
 import subprocess
 import sys
 from pathlib import Path
-from . import (METATYPES_JSON_SUFFIX, PROJECT_FILE_SUFFIX, TRANSLATION_SUFFIX,
-               qt_metatype_json_dir, MOD_CMD, QML_IMPORT_MAJOR_VERSION,
-               QML_IMPORT_MINOR_VERSION, QML_IMPORT_NAME, QT_MODULES)
+from . import (METATYPES_JSON_SUFFIX, PYPROJECT_JSON_PATTERN, PYPROJECT_TOML_PATTERN,
+               PYPROJECT_FILE_PATTERNS, TRANSLATION_SUFFIX, qt_metatype_json_dir, MOD_CMD,
+               QML_IMPORT_MAJOR_VERSION, QML_IMPORT_MINOR_VERSION, QML_IMPORT_NAME, QT_MODULES)
+from .pyproject_toml import parse_pyproject_toml
+from .pyproject_json import parse_pyproject_json
 
 
 def is_python_file(file: Path) -> bool:
@@ -19,7 +21,7 @@ def is_python_file(file: Path) -> bool:
 
 class ProjectData:
     def __init__(self, project_file: Path) -> None:
-        """Parse the project."""
+        """Parse the project file."""
         self._project_file = project_file.resolve()
         self._sub_projects_files: list[Path] = []
 
@@ -37,26 +39,39 @@ class ProjectData:
         # ts files
         self._ts_files: list[Path] = []
 
-        with project_file.open("r") as pyf:
-            pyproject = json.load(pyf)
-            for f in pyproject["files"]:
-                file = Path(project_file.parent / f)
-                if file.suffix == PROJECT_FILE_SUFFIX:
-                    self._sub_projects_files.append(file)
-                else:
-                    self._files.append(file)
-                    if file.suffix == ".qml":
-                        self._qml_files.append(file)
-                    elif is_python_file(file):
-                        if file.stem == "main":
-                            self.main_file = file
-                        self._python_files.append(file)
-                    elif file.suffix == ".ui":
-                        self._ui_files.append(file)
-                    elif file.suffix == ".qrc":
-                        self._qrc_files.append(file)
-                    elif file.suffix == TRANSLATION_SUFFIX:
-                        self._ts_files.append(file)
+        if project_file.match(PYPROJECT_JSON_PATTERN):
+            project_file_data = parse_pyproject_json(project_file)
+        elif project_file.match(PYPROJECT_TOML_PATTERN):
+            project_file_data = parse_pyproject_toml(project_file)
+        else:
+            print(f"Unknown project file format: {project_file}", file=sys.stderr)
+            sys.exit(1)
+
+        if project_file_data.errors:
+            print(f"Invalid project file: {project_file}. Errors found:", file=sys.stderr)
+            for error in project_file_data.errors:
+                print(f"{error}", file=sys.stderr)
+            sys.exit(1)
+
+        for f in project_file_data.files:
+            file = Path(project_file.parent / f)
+            if any(file.match(pattern) for pattern in PYPROJECT_FILE_PATTERNS):
+                self._sub_projects_files.append(file)
+                continue
+
+            self._files.append(file)
+            if file.suffix == ".qml":
+                self._qml_files.append(file)
+            elif is_python_file(file):
+                if file.stem == "main":
+                    self.main_file = file
+                self._python_files.append(file)
+            elif file.suffix == ".ui":
+                self._ui_files.append(file)
+            elif file.suffix == ".qrc":
+                self._qrc_files.append(file)
+            elif file.suffix == TRANSLATION_SUFFIX:
+                self._ts_files.append(file)
 
         if not self.main_file:
             self._find_main_file()
diff --git a/sources/pyside-tools/project_lib/pyproject_json.py b/sources/pyside-tools/project_lib/pyproject_json.py
new file mode 100644 (file)
index 0000000..2b0b9da
--- /dev/null
@@ -0,0 +1,58 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+import json
+from pathlib import Path
+
+from .pyproject_parse_result import PyProjectParseResult
+
+
+def write_pyproject_json(pyproject_file: Path, project_files: list[str]):
+    """
+    Create or update a *.pyproject file with the specified content.
+
+    :param pyproject_file: The *.pyproject file path to create or update.
+    :param project_files: The relative paths of the files to include in the project.
+    """
+    # The content of the file is fully replaced, so it is not necessary to read and merge any
+    # existing content
+    content = {
+        "files": sorted(project_files),
+    }
+    pyproject_file.write_text(json.dumps(content), encoding="utf-8")
+
+
+def parse_pyproject_json(pyproject_json_file: Path) -> PyProjectParseResult:
+    """
+    Parse a pyproject.json file and return a PyProjectParseResult object.
+    """
+    result = PyProjectParseResult()
+    try:
+        with pyproject_json_file.open("r") as pyf:
+            project_file_data = json.load(pyf)
+    except json.JSONDecodeError as e:
+        result.errors.append(str(e))
+        return result
+    except Exception as e:
+        result.errors.append(str(e))
+        return result
+
+    if not isinstance(project_file_data, dict):
+        result.errors.append("The root element of pyproject.json must be a JSON object")
+        return result
+
+    found_files = project_file_data.get("files")
+    if found_files and not isinstance(found_files, list):
+        result.errors.append("The files element must be a list")
+        return result
+
+    for file in project_file_data.get("files", []):
+        if not isinstance(file, str):
+            result.errors.append(f"Invalid file: {file}")
+            return result
+
+        file_path = Path(file)
+        if not file_path.is_absolute():
+            file_path = (pyproject_json_file.parent / file).resolve()
+        result.files.append(file_path)
+
+    return result
diff --git a/sources/pyside-tools/project_lib/pyproject_parse_result.py b/sources/pyside-tools/project_lib/pyproject_parse_result.py
new file mode 100644 (file)
index 0000000..6a04bf5
--- /dev/null
@@ -0,0 +1,10 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from dataclasses import dataclass, field
+from pathlib import Path
+
+
+@dataclass
+class PyProjectParseResult:
+    errors: list[str] = field(default_factory=list)
+    files: list[Path] = field(default_factory=list)
diff --git a/sources/pyside-tools/project_lib/pyproject_toml.py b/sources/pyside-tools/project_lib/pyproject_toml.py
new file mode 100644 (file)
index 0000000..fafe0d6
--- /dev/null
@@ -0,0 +1,235 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from __future__ import annotations
+
+import sys
+# TODO: Remove this import when Python 3.11 is the minimum supported version
+if sys.version_info >= (3, 11):
+    import tomllib
+from pathlib import Path
+
+from . import PYPROJECT_JSON_PATTERN
+from .pyproject_parse_result import PyProjectParseResult
+from .pyproject_json import parse_pyproject_json
+
+
+def _parse_toml_content(content: str) -> dict:
+    """
+    Parse TOML content for project name and files list only.
+    """
+    result = {"project": {}, "tool": {"pyside6-project": {}}}
+    current_section = None
+
+    for line in content.splitlines():
+        line = line.strip()
+        if not line or line.startswith('#'):
+            continue
+
+        if line == '[project]':
+            current_section = 'project'
+        elif line == '[tool.pyside6-project]':
+            current_section = 'tool.pyside6-project'
+        elif '=' in line and current_section:
+            key, value = [part.strip() for part in line.split('=', 1)]
+
+            # Handle string values - name of the project
+            if value.startswith('"') and value.endswith('"'):
+                value = value[1:-1]
+            # Handle array of strings - files names
+            elif value.startswith('[') and value.endswith(']'):
+                items = value[1:-1].split(',')
+                value = [item.strip().strip('"') for item in items if item.strip()]
+
+            if current_section == 'project':
+                result['project'][key] = value
+            else:  # tool.pyside6-project
+                result['tool']['pyside6-project'][key] = value
+
+    return result
+
+
+def _write_toml_content(data: dict) -> str:
+    """
+    Write minimal TOML content with project and tool.pyside6-project sections.
+    """
+    lines = []
+
+    if 'project' in data and data['project']:
+        lines.append('[project]')
+        for key, value in sorted(data['project'].items()):
+            if isinstance(value, str):
+                lines.append(f'{key} = "{value}"')
+
+    if 'tool' in data and 'pyside6-project' in data['tool']:
+        lines.append('\n[tool.pyside6-project]')
+        for key, value in sorted(data['tool']['pyside6-project'].items()):
+            if isinstance(value, list):
+                items = [f'"{item}"' for item in sorted(value)]
+                lines.append(f'{key} = [{", ".join(items)}]')
+            else:
+                lines.append(f'{key} = "{value}"')
+
+    return '\n'.join(lines)
+
+
+def parse_pyproject_toml(pyproject_toml_file: Path) -> PyProjectParseResult:
+    """
+    Parse a pyproject.toml file and return a PyProjectParseResult object.
+    """
+    result = PyProjectParseResult()
+
+    try:
+        content = pyproject_toml_file.read_text(encoding='utf-8')
+        # TODO: Remove the manual parsing when Python 3.11 is the minimum supported version
+        if sys.version_info >= (3, 11):
+            root_table = tomllib.loads(content)  # Use tomllib for Python >= 3.11
+            print("Using tomllib for parsing TOML content")
+        else:
+            root_table = _parse_toml_content(content)  # Fallback to manual parsing
+    except Exception as e:
+        result.errors.append(str(e))
+        return result
+
+    pyside_table = root_table.get("tool", {}).get("pyside6-project", {})
+    if not pyside_table:
+        result.errors.append("Missing [tool.pyside6-project] table")
+        return result
+
+    files = pyside_table.get("files", [])
+    if not isinstance(files, list):
+        result.errors.append("Missing or invalid files list")
+        return result
+
+    # Convert paths
+    for file in files:
+        if not isinstance(file, str):
+            result.errors.append(f"Invalid file: {file}")
+            return result
+        file_path = Path(file)
+        if not file_path.is_absolute():
+            file_path = (pyproject_toml_file.parent / file).resolve()
+        result.files.append(file_path)
+
+    return result
+
+
+def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files: list[str]):
+    """
+    Create or update a pyproject.toml file with the specified content.
+    """
+    data = {
+        "project": {"name": project_name},
+        "tool": {
+            "pyside6-project": {"files": sorted(project_files)}
+        }
+    }
+
+    try:
+        content = _write_toml_content(data)
+        pyproject_file.write_text(content, encoding='utf-8')
+    except Exception as e:
+        raise ValueError(f"Error writing TOML file: {str(e)}")
+
+
+def migrate_pyproject(pyproject_file: Path | str = None) -> int:
+    """
+    Migrate a project *.pyproject JSON file to the new pyproject.toml format.
+
+    The containing subprojects are migrated recursively.
+
+    :return: 0 if successful, 1 if an error occurred.
+    """
+    project_name = None
+
+    # Transform the user input string into a Path object
+    if isinstance(pyproject_file, str):
+        pyproject_file = Path(pyproject_file)
+
+    if pyproject_file:
+        if not pyproject_file.match(PYPROJECT_JSON_PATTERN):
+            print(f"Cannot migrate non \"{PYPROJECT_JSON_PATTERN}\" file:", file=sys.stderr)
+            print(f"\"{pyproject_file}\"", file=sys.stderr)
+            return 1
+        project_files = [pyproject_file]
+        project_name = pyproject_file.stem
+    else:
+        # Get the existing *.pyproject files in the current directory
+        project_files = list(Path().glob(PYPROJECT_JSON_PATTERN))
+        if not project_files:
+            print(f"No project file found in the current directory: {Path()}", file=sys.stderr)
+            return 1
+        if len(project_files) > 1:
+            print("Multiple pyproject files found in the project folder:")
+            print('\n'.join(str(project_file) for project_file in project_files))
+            response = input("Continue? y/n: ")
+            if response.lower().strip() not in {"yes", "y"}:
+                return 0
+        else:
+            # If there is only one *.pyproject file in the current directory,
+            # use its file name as the project name
+            project_name = project_files[0].stem
+
+    # The project files that will be written to the pyproject.toml file
+    output_files = set()
+    for project_file in project_files:
+        project_data = parse_pyproject_json(project_file)
+        if project_data.errors:
+            print(f"Invalid project file: {project_file}. Errors found:", file=sys.stderr)
+            print('\n'.join(project_data.errors), file=sys.stderr)
+            return 1
+        output_files.update(project_data.files)
+
+    project_folder = project_files[0].parent.resolve()
+    if project_name is None:
+        # If a project name has not resolved, use the name of the parent folder
+        project_name = project_folder.name
+
+    pyproject_toml_file = project_folder / "pyproject.toml"
+    if pyproject_toml_file.exists():
+        already_existing_file = True
+        try:
+            content = pyproject_toml_file.read_text(encoding='utf-8')
+            data = _parse_toml_content(content)
+        except Exception as e:
+            raise ValueError(f"Error parsing TOML: {str(e)}")
+    else:
+        already_existing_file = False
+        data = {"project": {}, "tool": {"pyside6-project": {}}}
+
+    # Update project name if not present
+    if "name" not in data["project"]:
+        data["project"]["name"] = project_name
+
+    # Update files list
+    data["tool"]["pyside6-project"]["files"] = sorted(
+        p.relative_to(project_folder).as_posix() for p in output_files
+    )
+
+    # Generate TOML content
+    toml_content = _write_toml_content(data)
+
+    if already_existing_file:
+        print(f"WARNING: A pyproject.toml file already exists at \"{pyproject_toml_file}\"")
+        print("The file will be updated with the following content:")
+        print(toml_content)
+        response = input("Proceed? [Y/n] ")
+        if response.lower().strip() not in {"yes", "y"}:
+            return 0
+
+    try:
+        pyproject_toml_file.write_text(toml_content)
+    except Exception as e:
+        print(f"Error writing to \"{pyproject_toml_file}\": {str(e)}", file=sys.stderr)
+        return 1
+
+    if not already_existing_file:
+        print(f"Created \"{pyproject_toml_file}\"")
+    else:
+        print(f"Updated \"{pyproject_toml_file}\"")
+
+    # Recursively migrate the subprojects
+    for sub_project_file in filter(lambda f: f.match(PYPROJECT_JSON_PATTERN), output_files):
+        result = migrate_pyproject(sub_project_file)
+        if result != 0:
+            return result
+    return 0
index f1e3f0c0ee53a26dae7fc047fdfd71200aa44938..c1c4065070a2890da39baf4238c2fabaf5619805 100644 (file)
@@ -2,23 +2,38 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 from __future__ import annotations
 
-import sys
 import subprocess
-from pathlib import Path
+import sys
 import xml.etree.ElementTree as ET
+from pathlib import Path
+
+from . import (QTPATHS_CMD, PYPROJECT_JSON_PATTERN, PYPROJECT_TOML_PATTERN, PYPROJECT_FILE_PATTERNS,
+               ClOptions)
+from .pyproject_toml import parse_pyproject_toml
+from .pyproject_json import parse_pyproject_json
+
 
-from . import QTPATHS_CMD, PROJECT_FILE_SUFFIX, ClOptions
+def run_command(command: list[str], cwd: str = None, ignore_fail: bool = False) -> int:
+    """
+    Run a command using a subprocess.
+    If dry run is enabled, the command will be printed to stdout instead of being executed.
 
+    :param command: The command to run including the arguments
+    :param cwd: The working directory to run the command in
+    :param ignore_fail: If True, the current process will not exit if the command fails
 
-def run_command(command: list[str], cwd: str = None, ignore_fail: bool = False):
-    """Run a command observing quiet/dry run"""
+    :return: The exit code of the command
+    """
     cloptions = ClOptions()
     if not cloptions.quiet or cloptions.dry_run:
         print(" ".join(command))
-    if not cloptions.dry_run:
-        ex = subprocess.call(command, cwd=cwd)
-        if ex != 0 and not ignore_fail:
-            sys.exit(ex)
+    if cloptions.dry_run:
+        return 0
+
+    ex = subprocess.call(command, cwd=cwd)
+    if ex != 0 and not ignore_fail:
+        sys.exit(ex)
+    return ex
 
 
 def qrc_file_requires_rebuild(resources_file_path: Path, compiled_resources_path: Path) -> bool:
@@ -46,7 +61,7 @@ def requires_rebuild(sources: list[Path], artifact: Path) -> bool:
         if source.stat().st_mtime > artifact_mod_time:
             return True
         # The .qrc file references other files that might have changed
-        if source.suffix == '.qrc' and qrc_file_requires_rebuild(source, artifact):
+        if source.suffix == ".qrc" and qrc_file_requires_rebuild(source, artifact):
             return True
     return False
 
@@ -115,13 +130,65 @@ def qt_metatype_json_dir() -> Path:
     return _qt_metatype_json_dir
 
 
-def resolve_project_file(cmdline: str) -> Path | None:
-    """Return the project file from the command  line value, either
-    from the file argument or directory"""
-    project_file = Path(cmdline).resolve() if cmdline else Path.cwd()
-    if project_file.is_file():
+def resolve_valid_project_file(
+    project_path_input: str = None, project_file_patterns: list[str] = PYPROJECT_FILE_PATTERNS
+) -> Path:
+    """
+    Find a valid project file given a preferred project file name and a list of project file name
+    patterns for a fallback search.
+
+    If the provided file name is a valid project file, return it. Otherwise, search for a known
+    project file in the current working directory with the given patterns.
+
+    Raises a ValueError if no project file is found, multiple project files are found in the same
+    directory or the provided path is not a valid project file or folder.
+
+    :param project_path_input: The command-line argument specifying a project file or folder path.
+    :param project_file_patterns: The list of project file patterns to search for.
+
+    :return: The resolved project file path
+    """
+    if project_path_input and (project_file := Path(project_path_input).resolve()).is_file():
+        if project_file.match(PYPROJECT_TOML_PATTERN):
+            if bool(parse_pyproject_toml(project_file).errors):
+                raise ValueError(f"Invalid project file: {project_file}")
+        elif project_file.match(PYPROJECT_JSON_PATTERN):
+            pyproject_json_result = parse_pyproject_json(project_file)
+            if errors := '\n'.join(str(e) for e in pyproject_json_result.errors):
+                raise ValueError(f"Invalid project file: {project_file}\n{errors}")
+        else:
+            raise ValueError(f"Unknown project file: {project_file}")
+        return project_file
+
+    project_folder = Path.cwd()
+    if project_path_input:
+        if not Path(project_path_input).resolve().is_dir():
+            raise ValueError(f"Invalid project path: {project_path_input}")
+        project_folder = Path(project_path_input).resolve()
+
+    # Search a project file in the project folder using the provided patterns
+    for pattern in project_file_patterns:
+        if not (matches := list(project_folder.glob(pattern))):
+            # No project files found with the specified pattern
+            continue
+
+        if len(matches) > 1:
+            matched_files = '\n'.join(str(f) for f in matches)
+            raise ValueError(f"Multiple project files found:\n{matched_files}")
+
+        project_file = matches[0]
+
+        if pattern == PYPROJECT_TOML_PATTERN:
+            if parse_pyproject_toml(project_file).errors:
+                # Invalid file, but a .pyproject file may exist
+                # We can not raise an error due to ensuring backward compatibility
+                continue
+        elif pattern == PYPROJECT_JSON_PATTERN:
+            pyproject_json_result = parse_pyproject_json(project_file)
+            if errors := '\n'.join(str(e) for e in pyproject_json_result.errors):
+                raise ValueError(f"Invalid project file: {project_file}\n{errors}")
+
+        # Found a valid project file
         return project_file
-    if project_file.is_dir():
-        for m in project_file.glob(f"*{PROJECT_FILE_SUFFIX}"):
-            return m
-    return None
+
+    raise ValueError("No project file found in the current directory")
index 887f2bdda4d127655dbce213e2abae052099ec70..5c009859d46862226144c262627cc5183ef1e76e 100644 (file)
@@ -198,6 +198,18 @@ def metaobjectdump():
     pyside_script_wrapper("metaobjectdump.py")
 
 
+def _check_requirements(requirements_file):
+    """Check if all required packages are installed."""
+    missing_packages = []
+    with open(requirements_file, 'r', encoding='UTF-8') as file:
+        for line in file:
+            # versions
+            package = line.strip().split('==')[0]
+            if not importlib.util.find_spec(package):
+                missing_packages.append(line.strip())
+    return missing_packages
+
+
 def project():
     pyside_script_wrapper("project.py")
 
@@ -220,12 +232,15 @@ def android_deploy():
               file=sys.stderr)
     else:
         android_requirements_file = Path(__file__).parent / "requirements-android.txt"
-        with open(android_requirements_file, 'r', encoding='UTF-8') as file:
-            while line := file.readline():
-                dependent_package = line.rstrip()
-                if not bool(importlib.util.find_spec(dependent_package)):
-                    command = [sys.executable, "-m", "pip", "install", dependent_package]
-                    subprocess.run(command)
+        if android_requirements_file.exists():
+            missing_packages = _check_requirements(android_requirements_file)
+            if missing_packages:
+                print("The following packages are required but not installed:")
+                for package in missing_packages:
+                    print(f"  - {package}")
+                print("Please install them using:")
+                print(f"  pip install -r {android_requirements_file}")
+                sys.exit(1)
         pyside_script_wrapper("android_deploy.py")
 
 
index 9ed5d8427548837afba8ac70391dd005ed5139a8..1a247f6c130fb3c4d32298bcba9fb71e7c98e86c 100644 (file)
@@ -1,3 +1,4 @@
 jinja2
 pkginfo
 tqdm
+packaging==24.1
index 86ca5ff45242d660c0e10ef9924fcc9bed038d77..f1458e935475c19b07de2a237b7f628b5e5b1f4a 100644 (file)
@@ -1,5 +1,5 @@
 set(pyside_MAJOR_VERSION "6")
-set(pyside_MINOR_VERSION "8")
-set(pyside_MICRO_VERSION "2.1")
+set(pyside_MINOR_VERSION "9")
+set(pyside_MICRO_VERSION "2")
 set(pyside_PRE_RELEASE_VERSION_TYPE "")
 set(pyside_PRE_RELEASE_VERSION "")
index f45c071148b53dbdbea759a2972a4e6fbad8e134..9c238e9807b8958ed5b2cf1133e0c4016a1abe0b 100644 (file)
@@ -25,6 +25,10 @@ if(Qt${QT_MAJOR_VERSION}Qml_FOUND)
     add_subdirectory(libpysideqml)
 endif()
 
+if(Qt${QT_MAJOR_VERSION}RemoteObjects_FOUND)
+    add_subdirectory(libpysideremoteobjects)
+endif()
+
 if(Qt${QT_MAJOR_VERSION}UiTools_FOUND)
     add_subdirectory(plugins/uitools)
     find_package(Qt6 COMPONENTS Designer)
index 37b7a6c9ccef21a21abbd873887708339d9ead02..e5b0b672e14c44a5df40d44b49a1f9437e7fcd61 100644 (file)
@@ -105,8 +105,6 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/templates/widgets_common.xml
         DESTINATION share/PySide6${pyside_SUFFIX}/typesystems)
 install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/templates/datavisualization_common.xml
         DESTINATION share/PySide6${pyside_SUFFIX}/typesystems)
-install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/templates/opengl_common.xml
-        DESTINATION share/PySide6${pyside_SUFFIX}/typesystems)
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pyside6_global.h
         DESTINATION include/${BINDING_NAME}${pyside6_SUFFIX})
 
index c74f6467b3c898a31600f52ce0e4df326e50b08d..88dd435415545fc3ba059b4ec4bf539798cf7ca4 100644 (file)
@@ -4,7 +4,7 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 -->
 
-<typesystem package="PySide6.Qt3DAnimation" doc-package="PySide6.Qt3D"
+<typesystem package="PySide6.Qt3DAnimation" doc-package="PySide6.Qt3D" doc-mode="flat"
             namespace-begin="QT_BEGIN_NAMESPACE" namespace-end="QT_END_NAMESPACE">
     <load-typesystem name="Qt3DRender/typesystem_3drender.xml" generate="no"/>
     <namespace-type name="Qt3DAnimation">
index 36c7fea979094c80bf511687838338895f903feb..65f3ccbc3d1815b08c8a4ae5ea06f40c4af61546 100644 (file)
@@ -598,7 +598,9 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
     def default_exception_handler(self, context: dict[str, Any]) -> None:
         # TODO
         if context["message"]:
-            print(context["message"])
+            print(f"{context['message']} from task {context['task']._name},"
+                  "read the following traceback:")
+            print(context["traceback"])
 
     def call_exception_handler(self, context: dict[str, Any]) -> None:
         if self._exception_handler is not None:
index be1809d5c3aa85e14fc99d5b192041430c61ff89..deabf690d81a211089665e1aae8d54a38296f07d 100644 (file)
@@ -4,6 +4,7 @@ from __future__ import annotations
 
 from . import events
 from . import futures
+import traceback
 
 from typing import Any
 
@@ -38,6 +39,8 @@ class QAsyncioTask(futures.QAsyncioFuture):
         self._cancelled = False  # PYSIDE-2644; see _step
         self._cancel_count = 0
         self._cancel_message: str | None = None
+        # Store traceback in case of Exception. Useful when exception happens in coroutine
+        self._tb: str = None
 
         # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support
         asyncio._register_task(self)  # type: ignore[arg-type]
@@ -113,6 +116,7 @@ class QAsyncioTask(futures.QAsyncioFuture):
         except BaseException as e:
             self._state = futures.QAsyncioFuture.FutureState.DONE_WITH_EXCEPTION
             self._exception = e
+            self._tb = traceback.format_exc()
         else:
             if asyncio.futures.isfuture(result):
                 # If the coroutine yields a future, the task will await its
@@ -159,7 +163,8 @@ class QAsyncioTask(futures.QAsyncioFuture):
                     "task": self,
                     "future": (exception_or_future
                                if asyncio.futures.isfuture(exception_or_future)
-                               else None)
+                               else None),
+                    "traceback": self._tb
                 })
 
             if self.done():
index d985b9778dd766050a86dea099d21388af88f591..d559f9d966aecfb40c0e3be510592c0b5ad34819 100644 (file)
@@ -203,6 +203,7 @@ ${QtCore_GEN_DIR}/qxmlstreamnamespacedeclaration_wrapper.cpp
 ${QtCore_GEN_DIR}/qxmlstreamnotationdeclaration_wrapper.cpp
 ${QtCore_GEN_DIR}/qxmlstreamreader_wrapper.cpp
 ${QtCore_GEN_DIR}/qxmlstreamwriter_wrapper.cpp
+${QtCore_GEN_DIR}/qmessagelogger_wrapper.cpp
 
 ${SPECIFIC_OS_FILES}
 # module is always needed
index c073c8bc10cfc63cc0056090f462add09667fda4..c51d227471ffa82299f86193417d6a287f623939 100644 (file)
@@ -45,7 +45,20 @@ QMetaType QVariant_resolveMetaType(PyTypeObject *type)
     // that has added any python fields or slots to its object layout.
     // See https://mail.python.org/pipermail/python-list/2009-January/520733.html
     if (type->tp_bases) {
-        for (Py_ssize_t i = 0, size = PyTuple_Size(type->tp_bases); i < size; ++i) {
+        const auto size = PyTuple_Size(type->tp_bases);
+        Py_ssize_t i = 0;
+        // PYSIDE-1887, PYSIDE-86: Skip QObject base class of QGraphicsObject;
+        // it needs to use always QGraphicsItem as a QVariant type for
+        // QGraphicsItem::itemChange() to work.
+        if (qstrcmp(typeName, "QGraphicsObject*") == 0 && size > 1) {
+            auto *firstBaseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, 0));
+            if (SbkObjectType_Check(firstBaseType)) {
+                const char *firstBaseTypeName = Shiboken::ObjectType::getOriginalName(firstBaseType);
+                if (firstBaseTypeName != nullptr && qstrcmp(firstBaseTypeName, "QObject*") == 0)
+                    ++i;
+            }
+        }
+        for ( ; i < size; ++i) {
             auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, i));
             const QMetaType derived = QVariant_resolveMetaType(baseType);
             if (derived.isValid())
index cbcbbf8f6eb6570daf1b3f7a9834a74b8ebb18cd..1544e6d448293e708a762da46411b8746a53fe05 100644 (file)
         <include file-name="qtcorehelper.h" location="local"/>
     </extra-includes>
 
-    <function signature="qFastCos(qreal)" since="4.6"/>
-    <function signature="qFastSin(qreal)" since="4.6"/>
+    <overload-removal type="QString" replaces="QStringView"/>
+    <overload-removal type="double" replaces="float"/>
+    <overload-removal type="unsigned int" replaces="unsigned short"/> <!-- Order is important here -->
+    <overload-removal type="unsigned long" replaces="unsigned short"/>
+    <overload-removal type="unsigned" replaces="unsigned short"/>
+    <overload-removal type="unsigned long long" replaces="unsigned;unsigned int;unsigned long"/>
+    <overload-removal type="int" replaces="short;std::chrono::milliseconds;std::chrono::seconds"/>
+    <overload-removal type="long" replaces="short"/>
+    <overload-removal type="long long" replaces="long;int"/>
+
+    <function signature="qFastCos(qreal)"/>
+    <function signature="qFastSin(qreal)"/>
     <function signature="qFuzzyCompare(double,double)"/>
-    <function signature="qFuzzyIsNull(double)" since="4.6"/>
+    <function signature="qFuzzyIsNull(double)"/>
     <function signature="qIsFinite(double)"/>
     <function signature="qIsInf(double)"/>
     <function signature="qIsNaN(double)"/>
     <function signature="qIsNull(double)"/>
     <!-- Qt5: gone <function signature="qRound(qreal)"/> -->
-    <function signature="qtTrId(const char*,int)" since="4.6"/>
+    <function signature="qtTrId(const char*,int)"/>
     <function signature="qVersion()">
         <modify-function>
             <modify-argument index="return" pyi-type="str"/>
   <rejection class="*" argument-type="Qt::Initialization"/>
 
   <rejection class="*" argument-type="FILE*"/>
-  <rejection class="*" argument-type="std::chrono::milliseconds"/>
   <rejection class="*" argument-type="^std::nullptr_t&amp;?$"/>
   <rejection class="*" argument-type="^std::initializer_list&lt;.*$"/>
 
   <primitive-type name="Qt::HANDLE" target-lang-api-name="PyLong"/>
   <?endif?>
 
+  <primitive-type name="std::chrono::milliseconds" target-lang-api-name="PyLong">
+    <extra-includes>
+        <include file-name="chrono" location="global"/>
+    </extra-includes>
+    <conversion-rule>
+        <native-to-target file="../glue/qtcore.cpp" snippet="chrono-to-pylong"/>
+        <target-to-native>
+            <add-conversion type="PyLong" file="../glue/qtcore.cpp"
+                            snippet="pylong-to-chrono"/>
+        </target-to-native>
+    </conversion-rule>
+  </primitive-type>
+
+  <primitive-type name="std::chrono::seconds" target-lang-api-name="PyLong">
+    <extra-includes>
+        <include file-name="chrono" location="global"/>
+    </extra-includes>
+    <conversion-rule>
+        <native-to-target file="../glue/qtcore.cpp" snippet="chrono-to-pylong"/>
+        <target-to-native>
+            <add-conversion type="PyLong" file="../glue/qtcore.cpp"
+                            snippet="pylong-to-chrono"/>
+        </target-to-native>
+    </conversion-rule>
+  </primitive-type>
+
   <!-- Qt5: add the new pointer-ish types -->
   <primitive-type name="qintptr" target-lang-api-name="PyLong">
     <conversion-rule>
       <include file-name="QtCore/QProperty" location="global"/>
     </extra-includes>
     <enum-type name="AlignmentFlag" python-type="IntFlag" flags="Alignment"/>
-    <enum-type name="AnchorPoint" since="4.6"/>
+    <enum-type name="AnchorPoint"/>
     <enum-type name="ColorScheme" since="6.5"/>
     <enum-type name="ApplicationAttribute"/>
     <enum-type name="ApplicationState" flags="ApplicationStates"/>
     <enum-type name="ConnectionType"/>
     <enum-type name="ContextMenuPolicy"/>
     <enum-type name="ContextMenuTrigger" since="6.8"/>
-    <enum-type name="CoordinateSystem" since="4.6"/>
+    <enum-type name="CoordinateSystem"/>
     <enum-type name="Corner"/>
     <enum-type name="CursorShape"/>
     <enum-type name="DateFormat"/>
     <enum-type name="FindChildOption" flags="FindChildOptions"/>
     <enum-type name="FocusPolicy" python-type="IntFlag"/>
     <enum-type name="FocusReason"/>
-    <enum-type name="GestureFlag" flags="GestureFlags" since="4.6"/>
-    <enum-type name="GestureState" since="4.6"/>
-    <enum-type name="GestureType" python-type="IntEnum" since="4.6"/>
+    <enum-type name="GestureFlag" flags="GestureFlags"/>
+    <enum-type name="GestureState"/>
+    <enum-type name="GestureType" python-type="IntEnum"/>
     <enum-type name="GlobalColor"/>
     <enum-type name="HighDpiScaleFactorRoundingPolicy"/>
     <enum-type name="HitTestAccuracy"/>
     <enum-type name="ImageConversionFlag" flags="ImageConversionFlags"/>
-    <enum-type name="InputMethodHint" flags="InputMethodHints" since="4.6"/>
+    <enum-type name="InputMethodHint" flags="InputMethodHints"/>
     <enum-type name="InputMethodQuery" flags="InputMethodQueries"/>
     <enum-type name="EnterKeyType"/>
     <enum-type name="ItemDataRole" python-type="IntEnum"/>
     <enum-type name="MouseEventFlag" flags="MouseEventFlags"/>
     <enum-type name="MouseEventSource"/>
     <enum-type name="NativeGestureType"/>
-    <enum-type name="NavigationMode" since="4.6"/>
+    <enum-type name="NavigationMode"/>
     <enum-type name="Orientation" flags="Orientations"/>
     <enum-type name="PenCapStyle"/>
     <enum-type name="PenJoinStyle"/>
     <enum-type name="TextFlag" python-type="IntFlag"/>
     <enum-type name="TextFormat"/>
     <enum-type name="TextInteractionFlag" flags="TextInteractionFlags"/>
-    <enum-type name="TileRule" since="4.6"/>
+    <enum-type name="TileRule"/>
     <enum-type name="TimerId" since="6.8"/>
     <enum-type name="TimerType"/>
     <enum-type name="TimeSpec"/>
     <enum-type name="ToolBarArea" flags="ToolBarAreas"/>
     <enum-type name="ToolBarAreaSizes"/>
     <enum-type name="ToolButtonStyle"/>
-    <enum-type name="TouchPointState" flags="TouchPointStates" since="4.6"/>
+    <enum-type name="TouchPointState" flags="TouchPointStates"/>
     <enum-type name="TransformationMode"/>
     <enum-type name="UIEffect"/>
     <enum-type name="WhiteSpaceMode"/>
     <enum-type name="WindowModality"/>
     <enum-type name="WindowState" flags="WindowStates"/>
     <enum-type name="WindowType" python-type="IntFlag" flags="WindowFlags"/>
-    <enum-type name="CursorMoveStyle" since="4.8" revision="4800"/>
+    <enum-type name="CursorMoveStyle" revision="4800"/>
 
     <inject-code class="target" position="end" file="../glue/qtcore.cpp"
                  snippet="qt-modifier"/>
     <enum-type name="ForeverConstant"/>
   </value-type>
 
-  <value-type name="QElapsedTimer" since="4.7">
-    <enum-type name="ClockType" since="4.7"/>
+  <value-type name="QElapsedTimer">
+    <enum-type name="ClockType"/>
   </value-type>
 
   <object-type name="QAbstractTableModel"
             <insert-template name="return_internal_pointer" />
         </inject-code>
     </modify-function>
+    <!-- Remove  const variation in favor of using internalPointer -->
+    <modify-function signature="constInternalPointer()const" remove="all"/>
     <modify-function signature="operator QModelIndex()const">
         <modify-argument index="return">
             <parent index="this" action="add"/>
     <enum-type name="LanguageCodeType" python-type="IntFlag" flags="LanguageCodeTypes" since="6.3"/>
     <enum-type name="MeasurementSystem"/>
     <enum-type name="NumberOption" flags="NumberOptions"/>
-    <enum-type name="Script" since="4.8" revision="4800"/>
-    <enum-type name="CurrencySymbolFormat" since="4.8" revision="4800"/>
-    <enum-type name="QuotationStyle" since="4.8" revision="4800"/>
+    <enum-type name="Script" revision="4800"/>
+    <enum-type name="CurrencySymbolFormat" revision="4800"/>
+    <enum-type name="QuotationStyle" revision="4800"/>
     <!--### All those C++ number types have the same representation in Python -->
     <modify-function signature="toString(qulonglong)const" remove="all"/>
     <modify-function signature="toString(ushort)const" remove="all"/>
     <configuration condition="QT_CONFIG(thread)"/>
     <inject-code file="../glue/qtcore.cpp" class="native" position="beginning" snippet="qthread_pthread_cleanup"/>
     <enum-type name="Priority"/>
+    <enum-type name="QualityOfService" since="6.9"/>
     <modify-function signature="currentThreadId()" remove="all"/>
     <modify-function signature="run()" allow-thread="yes">
         <inject-code file="../glue/qtcore.cpp" class="native" position="beginning"
         <inject-documentation format="target" mode="append"
                               file="../doc/qtcore.rst" snippet="qobject-findChild"/>
         <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-findchild-2"/>
-      <modify-argument index="return" pyi-type="Optional[PlaceHolderType]">
-        <parent index="this" action="add"/>
-      </modify-argument>
+        <modify-argument index="return" pyi-type="Optional[PlaceholderType]">
+            <parent index="this" action="add"/>
+        </modify-argument>
+        <modify-argument index="1" pyi-type="typing.Type[PlaceholderType]">
+        </modify-argument>
     </add-function>
     <add-function signature="findChildren(PyTypeObject*@type@,const QString&amp;@name@={},Qt::FindChildOptions@options@=Qt::FindChildrenRecursively)"
                   return-type="PySequence*" >
         Like the method *findChild*, the first parameter should be the child's type.
         </inject-documentation>
         <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-findchildren"/>
-        <modify-argument index="return" pyi-type="Iterable[PlaceHolderType]">
+        <modify-argument index="return" pyi-type="List[PlaceholderType]">
             <parent index="this" action="add"/>
         </modify-argument>
+        <modify-argument index="1" pyi-type="typing.Type[PlaceholderType]">
+        </modify-argument>
     </add-function>
     <add-function signature="findChildren(PyTypeObject*@type@,const QRegularExpression&amp;@pattern@,Qt::FindChildOptions@options@=Qt::FindChildrenRecursively)"
                   return-type="PySequence*" >
         <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-findchildren"/>
-        <modify-argument index="return" pyi-type="Iterable[PlaceHolderType]">
+        <modify-argument index="return" pyi-type="List[PlaceholderType]">
             <parent index="this" action="add"/>
         </modify-argument>
+        <modify-argument index="1" pyi-type="typing.Type[PlaceholderType]">
+        </modify-argument>
     </add-function>
 
     <add-function signature="tr(const char *@sourceText@, const char *@disambiguation@=nullptr, int @n@=-1)" return-type="QString" classmethod="yes">
             <insert-template name="return_native_eventfilter_conversion"/>
         </conversion-rule>
       </modify-argument>
-      <inject-code position="end">
-          <insert-template name="return_native_eventfilter"/>
-      </inject-code>
+      <inject-code position="end"
+                   file="../glue/qtcore.cpp" snippet="return-native-eventfilter"/>
     </modify-function>
   </object-type>
 
         <inject-code file="../glue/qtcore.cpp" snippet="qcryptographichash-adddata"/>
     </modify-function>
   </object-type>
-  <value-type name="QOperatingSystemVersionBase" since="6.3">
-      <enum-type name="OSType"/>
-  </value-type>
+  <value-type name="QOperatingSystemVersionBase" since="6.3"
+              default-constructor="QOperatingSystemVersionBase(QOperatingSystemVersionBase::Unknown, 1)"/>
   <value-type name="QOperatingSystemVersionUnexported" since="6.3" generate="false"/>
   <value-type name="QOperatingSystemVersion">
+      <enum-type name="OSType"/>
       <modify-function signature="QOperatingSystemVersion(const QOperatingSystemVersionBase&amp;)" remove="all"/>
   </value-type>
   <object-type name="QLibrary">
   </object-type>
   <object-type name="QLibraryInfo">
     <enum-type name="LibraryPath"/>
+    <inject-code class="native" position="beginning"
+                 file="../glue/qtcore.cpp" snippet="qlibraryinfo_python_build"/>
     <modify-function signature="build()">
         <inject-code class="target" position="end" file="../glue/qtcore.cpp" snippet="qlibraryinfo_build"/>
     </modify-function>
     <configuration condition="QT_CONFIG(thread)"/>
     <modify-function signature="lock()" allow-thread="yes"/>
     <modify-function signature="tryLock()" allow-thread="yes"/>
+    <modify-field name="FutexAlwaysAvailable" remove="yes"/>
   </object-type>
 
   <object-type name="QMutex">
   <object-type name="QCommandLineParser">
     <enum-type name="OptionsAfterPositionalArgumentsMode"/>
     <enum-type name="SingleDashWordOptionMode"/>
+    <enum-type name="MessageType" since="6.9"/>
   </object-type>
 
   <object-type name="QCoreApplication">
     <!--Qt5: gone <enum-type name="Encoding"/> -->
-    <enum-type identified-by-value="ApplicationFlags" since="4.8" revision="4800"/>
+    <enum-type identified-by-value="ApplicationFlags" revision="4800"/>
     <extra-includes>
       <include file-name="QStringList" location="global"/>
       <include file-name="QTranslator" location="global"/>
 
 
   <object-type name="QDataStream" stream="yes">
-    <enum-type name="FloatingPointPrecision" since="4.6"/>
+    <enum-type name="FloatingPointPrecision"/>
     <enum-type name="Status"/>
     <enum-type name="Version" python-type="IntEnum"/>
     <enum-type name="ByteOrder"/>
     <enum-type name="LocateOption" flags="LocateOptions"/>
   </object-type>
   <object-type name="QSystemSemaphore">
-    <configuration condition="#ifndef QT_NO_SYSTEMSEMAPHORE"/>
+    <configuration condition="#if QT_CONFIG(systemsemaphore)" />
     <enum-type name="AccessMode"/>
     <enum-type name="SystemSemaphoreError"/>
   </object-type>
   <object-type name="QXmlStreamReader">
     <enum-type name="Error"/>
     <enum-type name="TokenType"/>
-    <enum-type name="ReadElementTextBehaviour" since="4.6"/>
+    <enum-type name="ReadElementTextBehaviour"/>
   </object-type>
   <object-type name="QXmlStreamWriter">
     <!-- Removed because it expect QString to be mutable -->
     <add-function signature="__repr__" return-type="str">
         <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qmetaobject-repr"/>
     </add-function>
-    <modify-function signature="indexOfClassInfo(const char *)">
+    <modify-function signature="indexOfClassInfo(const char *)const">
         <modify-argument index="1" pyi-type="str"/>
     </modify-function>
-    <modify-function signature="indexOfConstructor(const char *)">
+    <modify-function signature="indexOfConstructor(const char *)const">
         <modify-argument index="1" pyi-type="str"/>
     </modify-function>
-    <modify-function signature="indexOfEnumerator(const char *)">
+    <modify-function signature="indexOfEnumerator(const char *)const">
         <modify-argument index="1" pyi-type="str"/>
     </modify-function>
-    <modify-function signature="indexOfMethod(const char *)">
+    <modify-function signature="indexOfMethod(const char *)const">
         <modify-argument index="1" pyi-type="str"/>
     </modify-function>
-    <modify-function signature="indexOfProperty(const char *)">
+    <modify-function signature="indexOfProperty(const char *)const">
         <modify-argument index="1" pyi-type="str"/>
     </modify-function>
-    <modify-function signature="indexOfSignal(const char *)">
+    <modify-function signature="indexOfSignal(const char *)const">
         <modify-argument index="1" pyi-type="str"/>
     </modify-function>
-    <modify-function signature="indexOfSlot(const char *)">
+    <modify-function signature="indexOfSlot(const char *)const">
         <modify-argument index="1" pyi-type="str"/>
     </modify-function>
   </object-type>
   </value-type>
 
   <!-- From Qt4.6 -->
-  <object-type name="QAbstractAnimation" since="4.6">
+  <object-type name="QAbstractAnimation">
     <enum-type name="DeletionPolicy"/>
     <enum-type name="Direction"/>
     <enum-type name="State"/>
   </object-type>
 
-  <object-type name="QAnimationGroup" since="4.6">
+  <object-type name="QAnimationGroup">
     <modify-function signature="addAnimation(QAbstractAnimation*)">
       <modify-argument index="1">
         <parent index="this" action="add"/>
   <!-- We will use inject code to implement the function below -->
   <rejection class="QEasingCurve" function-name="setCustomType"/>
   <rejection class="QEasingCurve" function-name="customType"/>
-  <value-type name="QEasingCurve" since="4.6">
+  <value-type name="QEasingCurve">
     <extra-includes>
       <include file-name="pysideweakref.h" location="global"/>
       <include file-name="glue/qeasingcurve_glue.h" location="local"/>
     </extra-includes>
   </value-type>
 
-  <value-type name="QMargins" since="4.6"/>
+  <value-type name="QMargins"/>
   <value-type name="QMarginsF"/>
 
-  <object-type name="QParallelAnimationGroup" since="4.6"/>
+  <object-type name="QParallelAnimationGroup"/>
 
-  <object-type name="QPauseAnimation" since="4.6"/>
+  <object-type name="QPauseAnimation"/>
 
-  <value-type name="QProcessEnvironment" since="4.6">
+  <value-type name="QProcessEnvironment">
       <configuration condition="QT_CONFIG(processenvironment)"/>
       <enum-type name="Initialization" since="6.3"/>
   </value-type>
 
-  <object-type name="QPropertyAnimation" since="4.6"/>
+  <object-type name="QPropertyAnimation"/>
 
-  <object-type name="QSequentialAnimationGroup" since="4.6"/>
+  <object-type name="QSequentialAnimationGroup"/>
 
-  <object-type name="QVariantAnimation" since="4.6"/>
+  <object-type name="QVariantAnimation"/>
 
   <value-type name="QVersionNumber">
     <modify-function signature="fromString(QAnyStringView,qsizetype*)">
   <object-type name="QStringListModel"/>
 
    <object-type name="QSharedMemory">
-       <configuration condition="#ifndef QT_NO_SHAREDMEMORY"/>
+       <configuration condition="#if QT_CONFIG(sharedmemory)" />
        <enum-type name="AccessMode"/>
        <enum-type name="SharedMemoryError"/>
        <modify-function signature="data()">
     </inject-code>
   </add-function>
 
+  <object-type name="QMessageLogger">
+    <inject-documentation format="target" mode="append"
+                          file="../doc/qtcore.rst" snippet="qmessagelogger"/>
+    <modify-function signature="debug(const char*)const">
+        <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+        <modify-argument index="1" pyi-type="str"/>
+    </modify-function>
+    <modify-function signature="debug(const QLoggingCategory&amp;,const char*)const">
+        <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-logcategory-format-string"/>
+        <modify-argument index="2" pyi-type="str"/>
+    </modify-function>
+    <modify-function signature="info(const char*)const">
+        <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+        <modify-argument index="1" pyi-type="str"/>
+    </modify-function>
+    <modify-function signature="info(const QLoggingCategory&amp;,const char*)const">
+        <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-logcategory-format-string"/>
+        <modify-argument index="2" pyi-type="str"/>
+    </modify-function>
+    <modify-function signature="warning(const char*)const">
+        <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+        <modify-argument index="1" pyi-type="str"/>
+    </modify-function>
+    <modify-function signature="warning(const QLoggingCategory&amp;,const char*)const">
+        <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-logcategory-format-string"/>
+        <modify-argument index="2" pyi-type="str"/>
+    </modify-function>
+    <modify-function signature="fatal(const char*)const">
+        <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+        <modify-argument index="1" pyi-type="str"/>
+    </modify-function>
+    <modify-function signature="fatal(const QLoggingCategory&amp;,const char*)const">
+        <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-logcategory-format-string"/>
+        <modify-argument index="2" pyi-type="str"/>
+    </modify-function>
+    <modify-function signature="critical(const char*)const">
+        <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+        <modify-argument index="1" pyi-type="str"/>
+    </modify-function>
+    <modify-function signature="critical(const QLoggingCategory&amp;,const char*)const">
+        <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-logcategory-format-string"/>
+        <modify-argument index="2" pyi-type="str"/>
+    </modify-function>
+    <modify-function signature="noDebug(const char*)const">
+        <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+        <modify-argument index="1" pyi-type="str"/>
+    </modify-function>
+  </object-type>
+
   <suppress-warning text="^.*enum 'Qt::Initialization' does not have a type entry.*$"/>
   <suppress-warning text="^.*Enum 'QRandomGenerator::System'.*does not have a type entry.*$"/>
 
index b32c39f887850ff47041869e56f2714ef239375a..29a7b2544ee8f9d3166da091c495cc967281efc7 100644 (file)
@@ -34,6 +34,7 @@ ${QtGraphs_GEN_DIR}/qlegenddata_wrapper.cpp
 ${QtGraphs_GEN_DIR}/qscatter3dseries_wrapper.cpp
 ${QtGraphs_GEN_DIR}/qscatterdataitem_wrapper.cpp
 ${QtGraphs_GEN_DIR}/qscatterdataproxy_wrapper.cpp
+${QtGraphs_GEN_DIR}/qspline3dseries_wrapper.cpp
 ${QtGraphs_GEN_DIR}/qsurface3dseries_wrapper.cpp
 ${QtGraphs_GEN_DIR}/qsurfacedataitem_wrapper.cpp
 ${QtGraphs_GEN_DIR}/qsurfacedataproxy_wrapper.cpp
index d929bcc0c2cdacd06a07062a4283d09803eb5ee7..81485a706aea26ffeed544099fc11e6ccd22d1bc 100644 (file)
@@ -12,8 +12,6 @@
 
   <load-typesystem name="QtQuick/typesystem_quick.xml" generate="no" />
 
-  <load-typesystem name="templates/datavisualization_common.xml" generate="no" />
-
   <function signature="qDefaultSurfaceFormat(bool)"/>
 
    <namespace-type name="QtGraphs3D">
@@ -23,6 +21,7 @@
      <enum-type name="RenderingMode"/>
      <enum-type name="SelectionFlag" flags="SelectionFlags"/>
      <enum-type name="ShadowQuality"/>
+     <enum-type name="TransparencyTechnique" since="6.7"/>
      <enum-type name="CameraPreset" since="6.7"/>
    </namespace-type>
 
   <object-type name="QScatterDataProxy">
   </object-type>
   <object-type name="QSplineSeries" since="6.8"/>
+  <object-type name="QSpline3DSeries" since="6.9"/>
   <object-type name="QSurface3DSeries">
     <enum-type name="Shading" since="6.8"/>
     <enum-type name="DrawFlag" flags="DrawFlags"/>
index 9400753860fa600b3b1d578b914673c973032a41..e3207856e3c4dfdfa5ad605ad396a6712dd213c8 100644 (file)
@@ -138,6 +138,7 @@ ${QtGui_GEN_DIR}/qfontdatabase_wrapper.cpp
 ${QtGui_GEN_DIR}/qfontinfo_wrapper.cpp
 ${QtGui_GEN_DIR}/qfontmetrics_wrapper.cpp
 ${QtGui_GEN_DIR}/qfontmetricsf_wrapper.cpp
+${QtGui_GEN_DIR}/qfontvariableaxis_wrapper.cpp
 ${QtGui_GEN_DIR}/qglyphrun_wrapper.cpp
 ${QtGui_GEN_DIR}/qgradient_wrapper.cpp
 ${QtGui_GEN_DIR}/qguiapplication_wrapper.cpp
@@ -191,6 +192,7 @@ ${QtGui_GEN_DIR}/qpainter_wrapper.cpp
 ${QtGui_GEN_DIR}/qpainterpath_element_wrapper.cpp
 ${QtGui_GEN_DIR}/qpainterpath_wrapper.cpp
 ${QtGui_GEN_DIR}/qpainterpathstroker_wrapper.cpp
+${QtGui_GEN_DIR}/qpainterstateguard_wrapper.cpp
 ${QtGui_GEN_DIR}/qpaintevent_wrapper.cpp
 ${QtGui_GEN_DIR}/qpalette_wrapper.cpp
 ${QtGui_GEN_DIR}/qpdfoutputintent_wrapper.cpp
index e5637cf23e474e9f09177ae5451e27a5539f11a6..5cf69940869e38f0c8c46f3bb31e42be5488c21d 100644 (file)
@@ -8,7 +8,6 @@
   <load-typesystem name="templates/common.xml" generate="no"/>
   <load-typesystem name="templates/core_common.xml" generate="no"/>
   <load-typesystem name="templates/gui_common.xml" generate="no"/>
-  <load-typesystem name="templates/opengl_common.xml" generate="no"/>
 
   <rejection class="^Q.*$" argument-type="^QPlatform.*$"/>
   <function signature="qAlpha(uint)"/>
   <object-type name="QAction">
     <enum-type name="ActionEvent"/>
     <enum-type name="MenuRole"/>
-    <enum-type name="Priority" since="4.6"/>
+    <enum-type name="Priority"/>
     <!-- PYSIDE-1627 QAction::menu()/setMenu() are templates -->
     <add-function signature="menu()const" return-type="QObject*">
         <inject-code file="../glue/qtgui.cpp" snippet="qaction-menu"/>
     </modify-function>
   </value-type>
   <value-type name="QTextBlockFormat">
-    <enum-type name="LineHeightTypes" since="4.8" revision="4800"/>
+    <enum-type name="LineHeightTypes" revision="4800"/>
     <enum-type name="MarkerType"/>
   </value-type>
   <value-type name="QTextTableCellFormat"/>
         <modify-argument index="1">
             <replace-type modified-type="PySequence"/>
         </modify-argument>
-        <inject-code class="target" position="beginning">
-            <insert-template name="load_xpm"/>
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="../glue/qtgui.cpp" snippet="qpixmap-load-xpm"/>
     </modify-function>
     <modify-function signature="QPixmap(const QString &amp;, const char *, QFlags&lt;Qt::ImageConversionFlag&gt;)">
         <modify-argument index="1"><replace-type modified-type="PyPathLike"/></modify-argument>
     <enum-type name="StyleHint"/>
     <enum-type name="StyleStrategy" python-type="Flag"/>
     <enum-type name="Weight" python-type="IntEnum"/>
-    <enum-type name="HintingPreference" since="4.8" revision="4800"/>
+    <enum-type name="HintingPreference" revision="4800"/>
     <extra-includes>
       <include file-name="QStringList" location="global"/>
     </extra-includes>
-    <value-type name="Tag" since="6.7"/>
+    <value-type name="Tag" since="6.7">
+        <inject-code class="native" position="beginning"
+                     file="../glue/qtgui.cpp" snippet="qfont-tag-from-str-helper"/>
+        <add-function signature="Tag(QString@name@)">
+            <inject-code class="target" position="beginning"
+                         file="../glue/qtgui.cpp" snippet="qfont-tag-init-str"/>
+        </add-function>
+        <add-function signature="fromString(QString@name@)"
+                      static="true" return-type="QFont::Tag">
+            <inject-code class="target" position="beginning"
+                         file="../glue/qtgui.cpp" snippet="qfont-tag-fromString"/>
+        </add-function>
+        <add-function signature="fromValue(int@value@)"
+                      static="true" return-type="QFont::Tag">
+            <inject-code class="target" position="beginning"
+                         file="../glue/qtgui.cpp" snippet="qfont-tag-fromValue"/>
+        </add-function>
+    </value-type>
     <!-- PYSIDE-1685: QFont(QString) should be checked first, else it will be interpreted as sequence -->
     <modify-function signature="QFont(const QString&amp;,int,int, bool)" overload-number="0"/>
     <modify-function signature="QFont(const QStringList &amp;,int,int, bool)" overload-number="1"/>
       </modify-argument>
     </modify-function>
   </value-type>
+  <value-type name="QFontVariableAxis" since="6.9"/>
   <value-type name="QTextTableCell" >
     <extra-includes>
       <include file-name="QTextCursor" location="global"/>
         <modify-argument index="1">
             <replace-type modified-type="PySequence"/>
         </modify-argument>
-        <inject-code class="target" position="beginning">
-            <insert-template name="load_xpm"/>
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="../glue/qtgui.cpp" snippet="qpixmap-load-xpm"/>
     </modify-function>
     <!-- ### There is already an fromData with a QByteArray type (that is convertible from Python's str) as the first type. -->
     <modify-function signature="fromData(const uchar*,int,const char*)" remove="all"/>
     <!-- ### There is already an loadFromData with a QByteArray type (that is convertible from Python's str) as the first type. -->
     <modify-function signature="loadFromData(const uchar*,int,const char*)" remove="all"/>
 
-    <modify-function signature="constBits()const" since="4.7">
+    <modify-function signature="constBits()const">
         <inject-code file="../glue/qtgui.cpp" snippet="qimage-constbits"/>
     </modify-function>
     <modify-function signature="bits()">
         <inject-code file="../glue/qtgui.cpp" snippet="qimage-bits"/>
     </modify-function>
-    <modify-function signature="constScanLine(int)const" since="4.7">
+    <modify-function signature="constScanLine(int)const">
         <inject-code file="../glue/qtgui.cpp" snippet="qimage-constscanline"/>
         <modify-argument index="return">
             <replace-type modified-type="PyObject"/>
       <include file-name="QVariant" location="global"/>
     </extra-includes>
     <add-function signature="__repr__" return-type="str">
-        <inject-code class="target" position="beginning">
-            <insert-template name="qcolor_repr"/>
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="../glue/qtgui.cpp" snippet="qcolor-repr"/>
     </add-function>
     <add-function signature="__str__" return-type="str">
-        <inject-code class="target" position="beginning">
-            <insert-template name="qcolor_repr"/>
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="../glue/qtgui.cpp" snippet="qcolor-repr"/>
     </add-function>
     <add-function signature="__setstate__(PyObject*)" return-type="PyObject">
         <inject-code file="../glue/qtgui.cpp" snippet="qcolor-setstate"/>
             </insert-template>
         </inject-code>
     </modify-function>
-    <modify-function signature="getHsl(int*,int*,int*,int*)const" since="4.6">
+    <modify-function signature="getHsl(int*,int*,int*,int*)const">
         <modify-argument index="0">
             <replace-type modified-type="PyObject*"/>
         </modify-argument>
             </insert-template>
         </inject-code>
     </modify-function>
-    <modify-function signature="getHslF(float*,float*,float*,float*)const" since="4.6">
+    <modify-function signature="getHslF(float*,float*,float*,float*)const">
         <modify-argument index="0">
             <replace-type modified-type="PyObject*"/>
         </modify-argument>
       <modify-argument index="1">
         <replace-type modified-type="char"/>
       </modify-argument>
-      <inject-code class="target" position="beginning">
-        <insert-template name="QFontCharFix"/>
-     </inject-code>
+      <inject-code class="target" position="beginning"
+                   file="../glue/qtgui.cpp" snippet="qfontmetrics-qfontcharfix"/>
     </modify-function>
 
     <modify-function signature="horizontalAdvance(QChar)const" rename="horizontalAdvanceChar">
       <modify-argument index="1">
         <replace-type modified-type="char"/>
       </modify-argument>
-      <inject-code class="target" position="beginning">
-        <insert-template name="QFontCharFix"/>
-     </inject-code>
+      <inject-code class="target" position="beginning"
+                   file="../glue/qtgui.cpp" snippet="qfontmetrics-qfontcharfix"/>
     </modify-function>
 
     <modify-function signature="boundingRect(QRectF,int,QString,int,int*)const">
       <modify-argument index="1">
         <replace-type modified-type="char"/>
       </modify-argument>
-      <inject-code class="target" position="beginning">
-        <insert-template name="QFontCharFix"/>
-     </inject-code>
+      <inject-code class="target" position="beginning"
+                   file="../glue/qtgui.cpp" snippet="qfontmetrics-qfontcharfix"/>
     </modify-function>
 
     <modify-function signature="horizontalAdvance(QChar)const" rename="horizontalAdvanceChar">
       <modify-argument index="1">
         <replace-type modified-type="char"/>
       </modify-argument>
-      <inject-code class="target" position="beginning">
-        <insert-template name="QFontCharFix"/>
-     </inject-code>
+      <inject-code class="target" position="beginning"
+                   file="../glue/qtgui.cpp" snippet="qfontmetrics-qfontcharfix"/>
     </modify-function>
 
     <modify-function signature="boundingRect(int,int,int,int,int,QString,int,int*)const">
   </object-type>
   <object-type name="QPyTextObject"/>
 
-  <object-type name="QDesktopServices" since="4.2">
+  <object-type name="QDesktopServices">
     <modify-function signature="openUrl(const QUrl&amp;)" allow-thread="yes"/>
   </object-type>
   <object-type name="QDoubleValidator">
     <enum-type name="MarkdownFeature" flags="MarkdownFeatures"/>
     <enum-type name="MetaInformation"/>
     <enum-type name="ResourceType" python-type="IntEnum"/>
-    <enum-type name="Stacks" since="4.7"/>
+    <enum-type name="Stacks"/>
     <modify-function signature="setDocumentLayout(QAbstractTextDocumentLayout*)">
       <modify-argument index="1">
         <parent index="this" action="add"/>
     </modify-function>
     <modify-function signature="print(QPagedPaintDevice*)const" allow-thread="yes" rename="print_"/>
   </object-type>
-  <object-type name="QTextDocumentWriter" since="4.5"/>
+  <object-type name="QTextDocumentWriter"/>
   <object-type name="QTextTable">
     <extra-includes>
       <include file-name="QTextCursor" location="global"/>
     <enum-type name="PaintEngineFeature" flags="PaintEngineFeatures"/>
     <enum-type name="PolygonDrawMode"/>
     <enum-type name="Type"/>
+
+    <modify-function signature="drawLines(const QLine*,int)">
+      <modify-argument index="1">
+        <replace-type modified-type="PySequence"/>
+        <conversion-rule class="native">
+          <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+            <replace from="%TYPE" to="QLine"/>
+          </insert-template>
+        </conversion-rule>
+        <conversion-rule class="target"> <!-- Virtual override -->
+          <insert-template name="c-array-to-pysequence">
+            <replace from="%TYPE" to="QLine"/>
+            <replace from="%COUNT" to="lineCount"/>
+          </insert-template>
+        </conversion-rule>
+      </modify-argument>
+      <modify-argument index="2">
+        <remove-argument/>
+        <conversion-rule class="native"> <!-- Provide parameter for binding -->
+          <insert-template name="pysequencesize_int"/>
+        </conversion-rule>
+      </modify-argument>
+    </modify-function>
+
+    <modify-function signature="drawLines(const QLineF*,int)">
+      <modify-argument index="1">
+        <replace-type modified-type="PySequence"/>
+        <conversion-rule class="native">
+          <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+            <replace from="%TYPE" to="QLineF"/>
+          </insert-template>
+        </conversion-rule>
+        <conversion-rule class="target"> <!-- Virtual override -->
+          <insert-template name="c-array-to-pysequence">
+            <replace from="%TYPE" to="QLineF"/>
+            <replace from="%COUNT" to="lineCount"/>
+          </insert-template>
+        </conversion-rule>
+      </modify-argument>
+      <modify-argument index="2">
+        <remove-argument/>
+        <conversion-rule class="native"> <!-- Provide parameter for binding -->
+          <insert-template name="pysequencesize_int"/>
+        </conversion-rule>
+      </modify-argument>
+    </modify-function>
+
+    <modify-function signature="drawPoints(const QPoint*,int)">
+      <modify-argument index="1">
+        <replace-type modified-type="PySequence"/>
+        <conversion-rule class="native">
+          <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+            <replace from="%TYPE" to="QPoint"/>
+          </insert-template>
+        </conversion-rule>
+        <conversion-rule class="target"> <!-- Virtual override -->
+          <insert-template name="c-array-to-pysequence">
+            <replace from="%TYPE" to="QPoint"/>
+            <replace from="%COUNT" to="pointCount"/>
+          </insert-template>
+        </conversion-rule>
+      </modify-argument>
+      <modify-argument index="2">
+        <remove-argument/>
+        <conversion-rule class="native"> <!-- Provide parameter for binding -->
+          <insert-template name="pysequencesize_int"/>
+        </conversion-rule>
+      </modify-argument>
+    </modify-function>
+
+    <modify-function signature="drawPoints(const QPointF*,int)">
+      <modify-argument index="1">
+        <replace-type modified-type="PySequence"/>
+        <conversion-rule class="native">
+          <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+            <replace from="%TYPE" to="QPointF"/>
+          </insert-template>
+        </conversion-rule>
+        <conversion-rule class="target"> <!-- Virtual override -->
+          <insert-template name="c-array-to-pysequence">
+            <replace from="%TYPE" to="QPointF"/>
+            <replace from="%COUNT" to="pointCount"/>
+          </insert-template>
+        </conversion-rule>
+      </modify-argument>
+      <modify-argument index="2">
+        <remove-argument/>
+        <conversion-rule class="native"> <!-- Provide parameter for binding -->
+          <insert-template name="pysequencesize_int"/>
+        </conversion-rule>
+      </modify-argument>
+    </modify-function>
+
+    <modify-function signature="drawPolygon(const QPoint*,int,QPaintEngine::PolygonDrawMode)">
+      <modify-argument index="1">
+        <replace-type modified-type="PySequence"/>
+        <conversion-rule class="native">
+          <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+            <replace from="%TYPE" to="QPoint"/>
+          </insert-template>
+        </conversion-rule>
+        <conversion-rule class="target"> <!-- Virtual override -->
+          <insert-template name="c-array-to-pysequence">
+            <replace from="%TYPE" to="QPoint"/>
+            <replace from="%COUNT" to="pointCount"/>
+          </insert-template>
+        </conversion-rule>
+      </modify-argument>
+      <modify-argument index="2">
+        <remove-argument/>
+        <conversion-rule class="native"> <!-- Provide parameter for binding -->
+          <insert-template name="pysequencesize_int"/>
+        </conversion-rule>
+      </modify-argument>
+    </modify-function>
+
+    <modify-function signature="drawPolygon(const QPointF*,int,QPaintEngine::PolygonDrawMode)">
+      <modify-argument index="1">
+        <replace-type modified-type="PySequence"/>
+        <conversion-rule class="native">
+          <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+            <replace from="%TYPE" to="QPointF"/>
+          </insert-template>
+        </conversion-rule>
+        <conversion-rule class="target"> <!-- Virtual override -->
+          <insert-template name="c-array-to-pysequence">
+            <replace from="%TYPE" to="QPointF"/>
+            <replace from="%COUNT" to="pointCount"/>
+          </insert-template>
+        </conversion-rule>
+      </modify-argument>
+      <modify-argument index="2">
+        <remove-argument/>
+        <conversion-rule class="native"> <!-- Provide parameter for binding -->
+          <insert-template name="pysequencesize_int"/>
+        </conversion-rule>
+      </modify-argument>
+    </modify-function>
+
+    <modify-function signature="drawRects(const QRect*,int)">
+      <modify-argument index="1">
+        <replace-type modified-type="PySequence"/>
+        <conversion-rule class="native">
+          <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+            <replace from="%TYPE" to="QRect"/>
+          </insert-template>
+        </conversion-rule>
+        <conversion-rule class="target"> <!-- Virtual override -->
+          <insert-template name="c-array-to-pysequence">
+            <replace from="%TYPE" to="QRect"/>
+            <replace from="%COUNT" to="rectCount"/>
+          </insert-template>
+        </conversion-rule>
+      </modify-argument>
+      <modify-argument index="2">
+        <remove-argument/>
+        <conversion-rule class="native"> <!-- Provide parameter for binding -->
+          <insert-template name="pysequencesize_int"/>
+        </conversion-rule>
+      </modify-argument>
+    </modify-function>
+
+    <modify-function signature="drawRects(const QRectF*,int)">
+      <modify-argument index="1">
+        <replace-type modified-type="PySequence"/>
+        <conversion-rule class="native">
+          <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+            <replace from="%TYPE" to="QRectF"/>
+          </insert-template>
+        </conversion-rule>
+        <conversion-rule class="target"> <!-- Virtual override -->
+          <insert-template name="c-array-to-pysequence">
+            <replace from="%TYPE" to="QRectF"/>
+            <replace from="%COUNT" to="rectCount"/>
+          </insert-template>
+        </conversion-rule>
+      </modify-argument>
+      <modify-argument index="2">
+        <remove-argument/>
+        <conversion-rule class="native"> <!-- Provide parameter for binding -->
+          <insert-template name="pysequencesize_int"/>
+        </conversion-rule>
+      </modify-argument>
+    </modify-function>
+
     <modify-function signature="begin(QPaintDevice*)">
       <modify-argument index="1" invalidate-after-use="yes"/>
     </modify-function>
       <include file-name="pyside_numpy.h" location="global"/>
     </extra-includes>
     <enum-type name="CompositionMode"/>
-    <enum-type name="PixmapFragmentHint" flags="PixmapFragmentHints" since="4.7"/>
+    <enum-type name="PixmapFragmentHint" flags="PixmapFragmentHints"/>
     <enum-type name="RenderHint" flags="RenderHints"/>
-    <value-type name="PixmapFragment" since="4.7">
+    <value-type name="PixmapFragment">
         <include file-name="QPainter" location="global"/>
     </value-type>
 
             <insert-template name="qpainter_drawlist"/>
         </inject-code>
     </add-function>
-    <!-- ### Overloads using QList<T> does the job of these methods -->
-    <modify-function signature="drawLines(const QLine*,int)" remove="all"/>
-    <modify-function signature="drawLines(const QPoint*,int)" remove="all"/>
-    <modify-function signature="drawLines(const QPointF*,int)" remove="all"/>
-    <modify-function signature="drawRects(const QRect*,int)" remove="all"/>
-    <!-- ### -->
+
     <!-- PYSIDE-1540: Preferably use the QPolygon overloads first to avoid
          a costly sequence type check on QPolygon. -->
     <modify-function signature="drawPoints(const QPoint*,int)" remove="all"/>
     </add-function>
   </object-type>
 
+  <object-type name="QPainterStateGuard" since="6.9">
+      <enum-type name="InitialState"/>
+      <add-function signature="__enter__()" return-type="QPainterStateGuard">
+          <inject-code file="../glue/qtcore.cpp" snippet="default-enter"/>
+      </add-function>
+      <add-function signature="__exit__(PyObject*,PyObject*,PyObject*)">
+          <inject-code file="../glue/qtgui.cpp" snippet="qpainterstateguard-restore"/>
+      </add-function>
+  </object-type>
+
   <value-type name="QGenericMatrix" generate="no"/>
-  <value-type name="QMatrix2x2" since="4.6">
+  <value-type name="QMatrix2x2">
     <modify-function signature="QMatrix2x2(const float*)" remove="all"/>
     <modify-function signature="copyDataTo(float*) const" remove="all"/>
     <modify-function signature="constData()const" remove="all"/>
     </add-function>
   </value-type>
 
-  <value-type name="QMatrix2x3" since="4.6">
+  <value-type name="QMatrix2x3">
     <modify-function signature="QMatrix2x3(const float*)" remove="all"/>
     <modify-function signature="copyDataTo(float*) const" remove="all"/>
     <modify-function signature="constData()const" remove="all"/>
     </add-function>
   </value-type>
 
-  <value-type name="QMatrix2x4" since="4.6">
+  <value-type name="QMatrix2x4">
     <modify-function signature="QMatrix2x4(const float*)" remove="all"/>
     <modify-function signature="copyDataTo(float*) const" remove="all"/>
     <modify-function signature="constData()const" remove="all"/>
     </add-function>
   </value-type>
 
-  <value-type name="QMatrix3x2" since="4.6">
+  <value-type name="QMatrix3x2">
     <modify-function signature="QMatrix3x2(const float*)" remove="all"/>
     <modify-function signature="copyDataTo(float*) const" remove="all"/>
     <modify-function signature="constData()const" remove="all"/>
     </add-function>
   </value-type>
 
-  <value-type name="QMatrix3x3" since="4.6">
+  <value-type name="QMatrix3x3">
     <modify-function signature="QMatrix3x3(const float*)" remove="all"/>
     <modify-function signature="copyDataTo(float*) const" remove="all"/>
     <modify-function signature="constData()const" remove="all"/>
     </add-function>
   </value-type>
 
-  <value-type name="QMatrix3x4" since="4.6">
+  <value-type name="QMatrix3x4">
     <modify-function signature="QMatrix3x4(const float*)" remove="all"/>
     <modify-function signature="copyDataTo(float*) const" remove="all"/>
     <modify-function signature="constData()const" remove="all"/>
     </add-function>
   </value-type>
 
-  <value-type name="QMatrix4x2" since="4.6">
+  <value-type name="QMatrix4x2">
     <modify-function signature="QMatrix4x2(const float*)" remove="all"/>
     <modify-function signature="copyDataTo(float*) const" remove="all"/>
     <modify-function signature="constData()const" remove="all"/>
     </add-function>
   </value-type>
 
-  <value-type name="QMatrix4x3" since="4.6">
+  <value-type name="QMatrix4x3">
     <modify-function signature="QMatrix4x3(const float*)" remove="all"/>
     <modify-function signature="copyDataTo(float*) const" remove="all"/>
     <modify-function signature="constData()const" remove="all"/>
     </add-function>
   </value-type>
 
-  <value-type name="QMatrix4x4" since="4.6">
+  <value-type name="QMatrix4x4">
     <enum-type name="Flag" flags="Flags"/>
 
     <!-- Qt5: HAIRY TRICK ALERT ahead!
 
     _______ end of matrix block _______ -->
 
-  <value-type name="QQuaternion" since="4.6">
+  <value-type name="QQuaternion">
     <add-function signature="__repr__" return-type="str">
         <inject-code class="target" position="beginning">
             <insert-template name="repr_code">
     </modify-function>
   </value-type>
 
-  <object-type name="QTouchEvent" since="4.6">
+  <object-type name="QTouchEvent">
       <add-function signature="__repr__" return-type="str">
           <inject-code class="target" position="beginning">
               <insert-template name="repr_qdebug_gui"/>
     <enum-type name="DeviceType" flags="DeviceTypes"/>
   </object-type>
 
-  <value-type name="QVector2D" since="4.6">
+  <value-type name="QVector2D">
     <add-function signature="__repr__" return-type="str">
         <inject-code class="target" position="beginning">
             <insert-template name="repr_code">
     </add-function>
 
   </value-type>
-  <value-type name="QVector3D" since="4.6">
+  <value-type name="QVector3D">
     <extra-includes>
       <include file-name="QMatrix4x4" location="global"/>
     </extra-includes>
     </add-function>
 
   </value-type>
-  <value-type name="QVector4D" since="4.6">
+  <value-type name="QVector4D">
     <extra-includes>
       <include file-name="QMatrix4x4" location="global"/>
     </extra-includes>
             <insert-template name="return_native_eventfilter_conversion"/>
         </conversion-rule>
       </modify-argument>
-      <inject-code position="end">
-          <insert-template name="return_native_eventfilter"/>
-      </inject-code>
+      <inject-code position="end"
+                   file="../glue/qtcore.cpp" snippet="return-native-eventfilter"/>
     </modify-function>
     <modify-function signature="fromWinId(WId)">
       <modify-argument index="1">
     <add-function signature="exec_()" return-type="int">
         <inject-code file="../glue/qtgui.cpp" snippet="qguiapplication-exec"/>
     </add-function>
+    <modify-function signature="modalWindow()">
+        <modify-argument index="return" pyi-type="Optional[PySide6.QtGui.QWindow]"/>
+    </modify-function>
     <add-function signature="nativeInterface()const" return-type="PyObject">
         <modify-argument index="return"> <!-- Suppress return value heuristics -->
             <define-ownership class="target" owner="default"/>
         <inject-code class="target" position="beginning" file="../glue/qtgui.cpp"
                      snippet="qguiapplication-nativeInterface"/>
     </add-function>
+    <modify-function signature="screenAt(const QPoint &amp;)">
+        <modify-argument index="return" pyi-type="Optional[PySide6.QtGui.QScreen]"/>
+    </modify-function>
     <modify-function signature="setOverrideCursor(const QCursor&amp;)">
         <modify-argument index="return" pyi-type="PyObject">
             <replace-type modified-type="QtGuiHelper::QOverrideCursorGuard*"/>
           <modify-argument index="return">
               <replace-type modified-type="QString"/>
           </modify-argument>
-          <inject-code class="target" position="beginning">
-            <insert-template name="glGetString_return_QString"/>
-          </inject-code>
+          <inject-code class="target" position="end" file="../glue/qtgui.cpp"
+                       snippet="glgetstring-return"/>
       </modify-function>
       <modify-function signature="glShaderBinary(GLint,const GLuint*,GLenum,const void*,GLint)">
           <modify-argument index="2"><array/></modify-argument>
index 8a8eb5e29700933d17b271983871ca65d49fce71..18082888bfb2fb292d838e519a99d077a5c3aa38 100644 (file)
 
     <object-type name="QMediaCaptureSession" since="6.1"/>
 
-    <object-type name="QMediaDevices" since="6.1"/>
+    <!-- FFMPEG query functions need threads. -->
+    <object-type name="QMediaDevices" since="6.1" allow-thread="true"/>
 
     <value-type name="QMediaFormat" since="6.1">
         <enum-type name="AudioCodec"/>
index 9f727547102201a70b66b14ba24e17ca90934115..0f54531269220d0b2af2be7bfde8ebe5f07dc184 100644 (file)
@@ -48,7 +48,7 @@
         <enum-type name="NetworkLayerProtocol"/>
         <enum-type name="PauseMode" flags="PauseModes"/>
         <enum-type name="SocketError"/>
-        <enum-type name="SocketOption" since="4.6"/>
+        <enum-type name="SocketOption"/>
         <enum-type name="SocketState"/>
         <enum-type name="SocketType"/>
         <modify-function signature="connectToHost(const QString&amp;,quint16,QFlags&lt;QIODeviceBase::OpenModeFlag>,QAbstractSocket::NetworkLayerProtocol)" allow-thread="yes"/>
 
     <value-type name="QNetworkAddressEntry">
         <enum-type name="DnsEligibilityStatus"/>
-        <configuration condition="#ifndef QT_NO_NETWORKINTERFACE"/>
+        <configuration condition="#if QT_CONFIG(networkinterface)"/>
     </value-type>
 
     <value-type name="QNetworkInterface">
         <enum-type name="InterfaceFlag" flags="InterfaceFlags"/>
         <enum-type name="InterfaceType"/>
-        <configuration condition="#ifndef QT_NO_NETWORKINTERFACE"/>
+        <configuration condition="#if QT_CONFIG(networkinterface)"/>
     </value-type>
 
     <value-type name="QNetworkProxy">
index f713ff10913d45c77dce4cb84c6fb3fd2ee96ff7..97a2e2bc0df24b6d8ec4de9e9a4070131bdaf8dc 100644 (file)
@@ -10,6 +10,7 @@ ${QtNetworkAuth_GEN_DIR}/qabstractoauthreplyhandler_wrapper.cpp
 ${QtNetworkAuth_GEN_DIR}/qoauth1_wrapper.cpp
 ${QtNetworkAuth_GEN_DIR}/qoauth1signature_wrapper.cpp
 ${QtNetworkAuth_GEN_DIR}/qoauth2authorizationcodeflow_wrapper.cpp
+${QtNetworkAuth_GEN_DIR}/qoauth2deviceauthorizationflow_wrapper.cpp
 ${QtNetworkAuth_GEN_DIR}/qoauthhttpserverreplyhandler_wrapper.cpp
 ${QtNetworkAuth_GEN_DIR}/qoauthoobreplyhandler_wrapper.cpp
 ${QtNetworkAuth_GEN_DIR}/qoauthurischemereplyhandler_wrapper.cpp
index 888f3d43d6ac937bbe0b4998e6e036c8b2070740..cf99acaf8f8c5ffe490811c83ae27fa4da56637f 100644 (file)
@@ -24,6 +24,7 @@
         </add-function>
     </object-type>
     <object-type name="QAbstractOAuth2">
+        <enum-type name="NonceMode" since="6.9"/>
         <modify-function signature="head(const QUrl&amp;,const QMap&lt;QString,QVariant&gt;&amp;)">
             <modify-argument index="return">
                 <define-ownership class="target" owner="default"/>
@@ -70,6 +71,7 @@
             </modify-argument>
         </modify-function>
     </object-type>
+    <object-type name="QOAuth2DeviceAuthorizationFlow" since="6.9"/>
     <object-type name="QAbstractOAuthReplyHandler"/>
     <object-type name="QOAuth1">
         <enum-type name="SignatureMethod"/>
index efbd16056f333906be70ea30f85f0e66cd288225..f6afdf83caf1537cb576008052fb8f4b64820327 100644 (file)
@@ -7,7 +7,6 @@
             namespace-begin="QT_BEGIN_NAMESPACE" namespace-end="QT_END_NAMESPACE">
   <load-typesystem name="QtCore/typesystem_core.xml" generate="no" />
   <load-typesystem name="QtGui/typesystem_gui.xml" generate="no" />
-  <load-typesystem name="templates/opengl_common.xml" generate="no" />
 
   <rejection class="^QOpenGL.*$" argument-type="^const GLboolean ?\*$"/>
   <rejection class="^QOpenGL.*$" argument-type="^GLchar\*$"/>
index 152efad9e07dfab8c6e34dd4e0aa170c50374684..ef6c9a0e53f662f23c3e7a24f0d746e960d65748 100644 (file)
@@ -6,9 +6,7 @@
     <modify-argument index="return">
         <replace-type modified-type="QString"/>
     </modify-argument>
-    <inject-code class="target" position="beginning">
-      <insert-template name="glGetString_return_QString"/>
-    </inject-code>
+    <inject-code class="target" position="end" file="../glue/qtgui.cpp" snippet="glgetstring-return"/>
 </modify-function>
 <modify-function signature="^glTexParameterI?u?[fi]v\(.*$">
    <modify-argument index="3"><array/></modify-argument>
index aa64abed296e49a5a1a8b3cbc1bdc679933d1b61..4f31cf7602c89c05b937dffae1c4a8aec6cad9a1 100644 (file)
@@ -28,8 +28,6 @@
     <enum-type name="PrintEnginePropertyKey"/>
   </object-type>
   <value-type name="QPrinterInfo"/>
-  <rejection class="QPrinter" function-name="printerSelectionOption"/>
-  <rejection class="QPrinter" function-name="setPrinterSelectionOption"/>
 
   <object-type name="QPrinter" >
     <enum-type name="ColorMode"/>
index 8477ac399f133338550493f3195fe383abfb6641..96bb81ed6ee4db05d76c2fc47cf060822285bbf6 100644 (file)
     <!-- Suppress anonymous enum warning -->
     <suppress-warning text="Anonymous enum (QmlCurrentSingletonTypeRegistrationVersion) does not have a type entry"/>
     <suppress-warning text="Enum 'QQmlModuleImportSpecialVersions' does not have a type entry"/>
+    <suppress-warning text="^Unable to decide type of property: .*QQmlListProperty.*Unable to translate type.*QQmlListProperty.*Cannot find type entry for.*QQmlListProperty.*$"/>
 </typesystem>
index 853c1589f7d96d5e14cf2f4932e0a6cdafe21a0d..87a6c51c2add3bcf17a2bcf05d91d48569815f9d 100644 (file)
     <suppress-warning text="^signature.*fromVulkanImage.*not found.*$"/>
     <suppress-warning text="^signature.*fromPhysicalDevice.*not found.*$"/>
     <suppress-warning text="^signature.*fromDeviceObjects.*not found.*$"/>
+    <suppress-warning text="^Unable to decide type of property:.*QQuickGradient.*Unable to translate type.*QQuickGradient.*Cannot find type entry for.*QQuickGradient.*$"/>
 
 </typesystem>
index 07835b2f6d83e871481935ec890290475b1b73fe..2522ab54ff7aa746ef1469e547caaf43f3511681 100644 (file)
@@ -29,20 +29,23 @@ ${QtRemoteObjects_GEN_DIR}/qtroserveriodevice_wrapper.cpp
 ${QtRemoteObjects_GEN_DIR}/qtremoteobjects_module_wrapper.cpp
 )
 
+find_package(Qt6 REQUIRED COMPONENTS Core)
+
 set(QtRemoteObjects_include_dirs ${QtRemoteObjects_SOURCE_DIR}
                                  ${QtRemoteObjects_BINARY_DIR}
                                  ${Qt${QT_MAJOR_VERSION}RemoteObjects_INCLUDE_DIRS}
+                                 ${libpysideremoteobjects_SOURCE_DIR}
                                  ${SHIBOKEN_INCLUDE_DIR}
                                  ${libpyside_SOURCE_DIR}
                                  ${SHIBOKEN_PYTHON_INCLUDE_DIR}
                                  ${QtCore_GEN_DIR}
                                  ${QtNetwork_GEN_DIR})
 
-set(QtRemoteObjects_libraries    pyside6
-                                 ${Qt${QT_MAJOR_VERSION}RemoteObjects_LIBRARIES})
-
 set(QtRemoteObjects_deps QtCore QtNetwork)
 
+set(QtRemoteObjects_libraries    pyside6 pyside6remoteobjects
+                                 ${Qt${QT_MAJOR_VERSION}RemoteObjects_LIBRARIES})
+
 create_pyside_module(NAME QtRemoteObjects
                      INCLUDE_DIRS QtRemoteObjects_include_dirs
                      LIBRARIES QtRemoteObjects_libraries
index 86e4d90939bf5224c97dda77061cdf5330c82646..a6e54ee18b6154ccb98214c1b7bfa463631cb8b1 100644 (file)
@@ -8,6 +8,9 @@
     <load-typesystem name="templates/core_common.xml" generate="no"/>
     <load-typesystem name="QtCore/typesystem_core.xml" generate="no"/>
     <load-typesystem name="QtNetwork/typesystem_network.xml" generate="no"/>
+    <inject-code class="native" position="beginning">
+    #include "pysideremoteobjects.h"
+    </inject-code>
 
     <rejection class="QRemoteObjectStringLiterals"/>
     <rejection class="*" function-name="getTypeNameAndMetaobjectFromClassInfo"/>
     </object-type>
     <object-type name="QRemoteObjectNode">
         <enum-type name="ErrorCode"/>
+        <add-function signature="acquire(PyTypeObject*, PyObject* @name@ = 0)"
+                      return-type="PyTypeObject*">
+            <inject-code class="target" file="../glue/qtremoteobjects.cpp" snippet="node-acquire"/>
+        </add-function>
     </object-type>
     <object-type name="QRemoteObjectPendingCall">
         <enum-type name="Error"/>
     <object-type name="QRemoteObjectRegistryHost"/>
     <object-type name="QRemoteObjectReplica">
         <enum-type name="State"/>
-        <!-- protected: <enum-type name="ConstructorType"/>  -->
+        <enum-type name="ConstructorType" python-type="IntEnum"/> <!-- Needed even though protected -->
+        <modify-function signature="QRemoteObjectReplica(QRemoteObjectReplica::ConstructorType)">
+            <modify-argument index="1">
+                <replace-default-expression with="{}"/>
+            </modify-argument>
+        </modify-function>
     </object-type>
     <object-type name="QRemoteObjectSettingsStore"/>
     <value-type name="QRemoteObjectSourceLocationInfo"/>
@@ -53,4 +65,7 @@
     <!-- QtNetwork is pulled in via QtRemoteObjectsDepends. -->
     <suppress-warning text="^Scoped enum 'Q(Ocsp)|(Dtls).*' does not have a type entry.*$"/>
 
+    <inject-code class="target" position="end"
+                 file="../glue/qtremoteobjects.cpp" snippet="qtro-init"/>
+
 </typesystem>
index 6cf562c52924637404a56c382adfedd2f95cbbac..8326b1a586ec94a1a3f44efacf84a7af102090ee 100644 (file)
@@ -86,6 +86,8 @@
         <value-type name="TimeStamp"/>
     </value-type>
     <object-type name="QCanDbcFileParser">
+        <modify-function signature="parse(const QString&amp;)" overload-number="0"/>
+        <modify-function signature="parse(const QStringList&amp;)" overload-number="1"/>
         <enum-type name="Error"/>
     </object-type>
     <object-type name="QCanFrameProcessor">
index 173392f3b739e3c748bf7fac47c4be02daa90486..dbf4ef6ca2e5bf4565fe125054edf8679aeb9b63 100644 (file)
   </object-type>
 
   <namespace-type name="QTest">
-    <!-- Qt5: private <enum-type name="AttributeIndex" since="4.6"/> -->
+    <!-- Qt5: private <enum-type name="AttributeIndex"/> -->
     <enum-type name="KeyAction"/>
-    <!-- Qt5: private <enum-type name="LogElementType" since="4.6"/> -->
+    <!-- Qt5: private <enum-type name="LogElementType"/> -->
     <enum-type name="MouseAction"/>
-    <enum-type name="QBenchmarkMetric" since="4.7"/>
+    <enum-type name="QBenchmarkMetric"/>
     <enum-type name="TestFailMode"/>
     <enum-type name="ComparisonOperation" since="6.4"/>
     <extra-includes>
         <include file-name="QtTest" location="global"/>
     </extra-includes>
 
-    <object-type name="PySideQTouchEventSequence" target-lang-name="QTouchEventSequence" since="4.6" >
+    <object-type name="PySideQTouchEventSequence" target-lang-name="QTouchEventSequence">
         <modify-function signature="press(int,const QPoint&amp;,QWidget*)">
             <modify-argument index="return">
                 <define-ownership class="target" owner="default"/>
             </modify-argument>
         </modify-function>
     </object-type>
-    <modify-function signature="generateTouchEvent(QWidget*,QPointingDevice*,bool)" rename="touchEvent" since="4.6">
+    <modify-function signature="generateTouchEvent(QWidget*,QPointingDevice*,bool)" rename="touchEvent">
         <modify-argument index="return">
             <define-ownership class="target" owner="target"/>
         </modify-argument>
     </modify-function>
-    <modify-function signature="generateTouchEvent(QWindow*,QPointingDevice*,bool)" rename="touchEvent" since="4.6">
+    <modify-function signature="generateTouchEvent(QWindow*,QPointingDevice*,bool)" rename="touchEvent">
         <modify-argument index="return">
             <define-ownership class="target" owner="target"/>
         </modify-argument>
index 9cc3d87e9976c061c91b3e74750d91af4d825457..7afb9ef108791b0d45ab412e6be03f3ac12e1127 100644 (file)
@@ -37,6 +37,7 @@ ${QtWebEngineCore_GEN_DIR}/qwebenginenotification_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebenginepage_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebenginepermission_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebengineprofile_wrapper.cpp
+${QtWebEngineCore_GEN_DIR}/qwebengineprofilebuilder_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebenginequotarequest_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebengineregisterprotocolhandlerrequest_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebenginescript_wrapper.cpp
index 36c15c4e96d06b2ee613a906c58054046328e210..003c18deb0947e7479fde2acc0fe6edffa68afd5 100644 (file)
     </add-function>
   </object-type>
 
+  <object-type name="QWebEngineProfileBuilder" since="6.9"/>
+
   <object-type name="QWebEngineNewWindowRequest">
       <enum-type name="DestinationType"/>
   </object-type>
index d87dc55a45d9d51635997fc9276a3db092f83f12..77152e9f6336ccf5310b8c44cdbf97cbe113cc4b 100644 (file)
@@ -4,6 +4,7 @@
 project(QtWebEngineQuick)
 
 set(QtWebEngineQuick_SRC
+${QtWebEngineQuick_GEN_DIR}/qquickwebenginedownloadrequest_wrapper.cpp
 ${QtWebEngineQuick_GEN_DIR}/qquickwebengineprofile_wrapper.cpp
 ${QtWebEngineQuick_GEN_DIR}/qtwebenginequick_wrapper.cpp
 # module is always needed
index 3d383337b4e0bb4c0b56b394c0d77dfd4b0d7162..ae6b4122f6c35bb24d36705d8c77f3b711be447f 100644 (file)
@@ -15,5 +15,7 @@
         <enum-type name="PersistentCookiesPolicy"/>
         <enum-type name="PersistentPermissionsPolicy" since="6.8"/>
     </object-type>
+    <object-type name="QQuickWebEngineDownloadRequest" since="6.9"/>
+
 
 </typesystem>
index 7879cdebd1b4971b4a93f33ad9e30e426d318914..f1b9e14daaa0c32ee582733ea554bc84b05b7bfc 100644 (file)
@@ -30,8 +30,6 @@
     enum 'QGraphicsPolygonItem::Type' does not have a type entry or is not an enum
   """
   -->
-  <rejection class="QMdi"/>
-
   <function signature="qDrawShadeLine(QPainter*,int,int,int,int,const QPalette&amp;,bool,int,int)"/>
   <function signature="qDrawShadeLine(QPainter*,const QPoint,const QPoint,const QPalette&amp;,bool,int,int)"/>
   <function signature="qDrawShadeRect(QPainter*,int,int,int,int,const QPalette&amp;,bool,int,int,const QBrush*)"/>
     <enum-type name="Extension"/>
     <enum-type name="GraphicsItemChange"/>
     <enum-type name="GraphicsItemFlag" flags="GraphicsItemFlags"/>
-    <enum-type name="PanelModality" since="4.6"/>
+    <enum-type name="PanelModality"/>
     <inject-code class="target" position="end" file="../glue/qtwidgets.cpp" snippet="qgraphicsitem"/>
     <modify-function signature="setParentItem(QGraphicsItem*)">
       <modify-argument index="this">
     <modify-function signature="inputMethodEvent(QInputMethodEvent*)">
       <modify-argument index="1" invalidate-after-use="yes"/>
     </modify-function>
-    <modify-function signature="isBlockedByModalPanel(QGraphicsItem**)const" since="4.6">
+    <modify-function signature="isBlockedByModalPanel(QGraphicsItem**)const">
       <modify-argument index="1">
         <remove-argument/>
       </modify-argument>
     </modify-function>
     <modify-function signature="setItemDelegateForColumn(int,QAbstractItemDelegate*)">
       <modify-argument index="2">
-        <reference-count action="set"/>
+        <reference-count action="add"/>
       </modify-argument>
     </modify-function>
     <modify-function signature="setItemDelegateForRow(int,QAbstractItemDelegate*)">
       <modify-argument index="2">
-        <reference-count action="set"/>
+        <reference-count action="add"/>
       </modify-argument>
     </modify-function>
     <modify-function signature="model()const">
         <parent index="this" action="add"/>
       </modify-argument>
     </modify-function>
-    <!-- this fuction is declared when not defined QT_NO_TOOLBA -->
+    <!-- this function is declared when not defined QT_NO_TOOLBAR -->
     <modify-function signature="addToolBar(Qt::ToolBarArea,QToolBar*)">
       <modify-argument index="2">
         <parent index="this" action="add"/>
                polymorphic-id-expression="%B-&gt;type() == QEvent::GraphicsSceneWheel"/>
 
   <object-type name="QGestureEvent"
-               polymorphic-id-expression="%B-&gt;type() == QEvent::Gesture || %B-&gt;type() == QEvent::GestureOverride" since="4.6">
+               polymorphic-id-expression="%B-&gt;type() == QEvent::Gesture || %B-&gt;type() == QEvent::GestureOverride">
     <modify-function signature="activeGestures()const">
       <modify-argument index="return">
         <define-ownership owner="default"/>
     <enum-type name="ControlElement" python-type="IntEnum"/>
     <enum-type name="PixelMetric" python-type="IntEnum"/>
     <enum-type name="PrimitiveElement" python-type="IntEnum"/>
-    <enum-type name="RequestSoftwareInputPanel" since="4.6"/>
+    <enum-type name="RequestSoftwareInputPanel"/>
     <enum-type name="StandardPixmap" python-type="IntEnum"/>
     <enum-type name="StateFlag" flags="State"/>
     <enum-type name="StyleHint" python-type="IntEnum"/>
         <modify-argument index="3">
             <replace-type modified-type="PySequence"/>
             <conversion-rule class="native">
-                <insert-template name="qgraphicsitem_pysequence"/>
+                <insert-template name="pysequence-to-c-array">
+                    <replace from="%TYPE" to="QGraphicsItem*"/>
+                </insert-template>
             </conversion-rule>
 
             <conversion-rule class="target">
-                <insert-template name="qgraphicsitem_pyobject"/>
+                <insert-template name="c-array-to-pysequence">
+                    <replace from="%TYPE" to="QGraphicsItem*"/>
+                    <replace from="%COUNT" to="numItems"/>
+                </insert-template>
             </conversion-rule>
         </modify-argument>
 
     <enum-type name="InputMode"/>
 
     <modify-function signature="getInt(QWidget*,const QString&amp;,const QString&amp;,int,int,int,int,bool*,QFlags&lt;Qt::WindowType&gt;)" allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
       <modify-argument index="return" pyi-type="Tuple[int, bool]"/>
       <modify-argument index="8">
         <remove-default-expression/>
     </modify-function>
 
     <modify-function signature="getItem(QWidget*,const QString&amp;,const QString&amp;,const QStringList&amp;,int,bool,bool*,QFlags&lt;Qt::WindowType&gt;,QFlags&lt;Qt::InputMethodHint&gt;)" allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
       <modify-argument index="return" pyi-type="Tuple[str, bool]"/>
       <modify-argument index="7">
         <remove-default-expression/>
     </modify-function>
 
     <modify-function signature="getMultiLineText(QWidget*,const QString&amp;,const QString&amp;,const QString&amp;,bool*,QFlags&lt;Qt::WindowType&gt;,QFlags&lt;Qt::InputMethodHint&gt;)" allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
       <modify-argument index="return" pyi-type="Tuple[str, bool]"/>
       <modify-argument index="5">
         <remove-default-expression/>
     </modify-function>
 
     <modify-function signature="getText(QWidget*,const QString&amp;,const QString&amp;,QLineEdit::EchoMode,const QString&amp;,bool*,QFlags&lt;Qt::WindowType&gt;,QFlags&lt;Qt::InputMethodHint&gt;)" allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
       <modify-argument index="return" pyi-type="Tuple[str, bool]"/>
       <modify-argument index="6">
         <remove-default-expression/>
     </modify-function>
 
     <modify-function signature="getDouble(QWidget*,const QString&amp;,const QString&amp;,double,double,double,int,bool*,QFlags&lt;Qt::WindowType&gt;,double)" allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
       <modify-argument index="return" pyi-type="Tuple[float, bool]"/>
       <modify-argument index="8">
         <remove-default-expression/>
       </modify-argument>
     </modify-function>
     <modify-function signature="setItemWidget(QTreeWidgetItem*,int,QWidget*)" allow-thread="yes">
-      <modify-argument index="3">
+      <modify-argument index="3" pyi-type="Optional[PySide6.QtWidgets.QWidget]">
         <parent index="this" action="add"/>
       </modify-argument>
     </modify-function>
       </modify-argument>
     </modify-function>
     <modify-function signature="setItemWidget(QListWidgetItem*,QWidget*)" allow-thread="yes">
-      <modify-argument index="2">
+      <modify-argument index="2" pyi-type="Optional[PySide6.QtWidgets.QWidget]">
         <parent index="1" action="add"/>
       </modify-argument>
     </modify-function>
             <insert-template name="return_native_eventfilter_conversion"/>
         </conversion-rule>
       </modify-argument>
-      <inject-code position="end">
-          <insert-template name="return_native_eventfilter"/>
-      </inject-code>
+      <inject-code position="end"
+                   file="../glue/qtcore.cpp" snippet="return-native-eventfilter"/>
     </modify-function>
 
     <extra-includes>
                      snippet="qmessagebox-open-connect-accept"/>
     </add-function>
     <!-- FIXME PYSIDE-7: Remove deprecated overloads -->
-    <modify-function signature="critical(QWidget*,const QString&amp;,const QString&amp;,QFlags&lt;QMessageBox::StandardButton&gt;,QMessageBox::StandardButton)" allow-thread="yes"/>
+    <modify-function signature="critical(QWidget*,const QString&amp;,const QString&amp;,QFlags&lt;QMessageBox::StandardButton&gt;,QMessageBox::StandardButton)" allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+    </modify-function>
     <modify-function signature="critical(QWidget*,const QString&amp;,const QString&amp;,QMessageBox::StandardButton,QMessageBox::StandardButton)"
-                     allow-thread="yes"/>
-    <modify-function signature="information(QWidget*,const QString&amp;,const QString&amp;,QFlags&lt;QMessageBox::StandardButton&gt;,QMessageBox::StandardButton)" allow-thread="yes"/>
+                     allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+    </modify-function>
+    <modify-function signature="information(QWidget*,const QString&amp;,const QString&amp;,QFlags&lt;QMessageBox::StandardButton&gt;,QMessageBox::StandardButton)" allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+    </modify-function>
     <modify-function signature="information(QWidget*,const QString&amp;,const QString&amp;,QMessageBox::StandardButton,QMessageBox::StandardButton)"
-                     allow-thread="yes"/>
-    <modify-function signature="question(QWidget*,const QString&amp;,const QString&amp;,QFlags&lt;QMessageBox::StandardButton&gt;,QMessageBox::StandardButton)" allow-thread="yes"/>
+                     allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+    </modify-function>
+    <modify-function signature="question(QWidget*,const QString&amp;,const QString&amp;,QFlags&lt;QMessageBox::StandardButton&gt;,QMessageBox::StandardButton)" allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+    </modify-function>
     <modify-function signature="question(QWidget*,const QString&amp;,const QString&amp;,QMessageBox::StandardButton,QMessageBox::StandardButton)"
-                     allow-thread="yes"/>
-    <modify-function signature="warning(QWidget*,const QString&amp;,const QString&amp;,QFlags&lt;QMessageBox::StandardButton&gt;,QMessageBox::StandardButton)" allow-thread="yes"/>
+                     allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+    </modify-function>
+    <modify-function signature="warning(QWidget*,const QString&amp;,const QString&amp;,QFlags&lt;QMessageBox::StandardButton&gt;,QMessageBox::StandardButton)" allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+    </modify-function>
     <modify-function signature="warning(QWidget*,const QString&amp;,const QString&amp;,QMessageBox::StandardButton,QMessageBox::StandardButton)"
-                     allow-thread="yes"/>
+                     allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+    </modify-function>
     <modify-function signature="QMessageBox(const QString&amp;,const QString&amp;,QMessageBox::Icon,int,int,int,QWidget*,QFlags&lt;Qt::WindowType&gt;)" remove="all"/>
     <modify-function signature="critical(QWidget*,const QString&amp;,const QString&amp;,int,int,int)" remove="all"/>
     <modify-function signature="critical(QWidget*,const QString&amp;,const QString&amp;,const QString&amp;,const QString&amp;,const QString&amp;,int,int)" remove="all"/>
     <modify-function signature="question(QWidget*,const QString&amp;,const QString&amp;,const QString&amp;,const QString&amp;,const QString&amp;,int,int)" remove="all"/>
     <modify-function signature="warning(QWidget*,const QString&amp;,const QString&amp;,int,int,int)" remove="all"/>
     <modify-function signature="warning(QWidget*,const QString&amp;,const QString&amp;,const QString&amp;,const QString&amp;,const QString&amp;,int,int)" remove="all"/>
-    <modify-function signature="about(QWidget*,const QString&amp;,const QString&amp;)" allow-thread="yes"/>
-    <modify-function signature="aboutQt(QWidget*,const QString&amp;)" allow-thread="yes"/>
+    <modify-function signature="about(QWidget*,const QString&amp;,const QString&amp;)" allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+    </modify-function>
+    <modify-function signature="aboutQt(QWidget*,const QString&amp;)" allow-thread="yes">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+    </modify-function>
   </object-type>
   <object-type name="QAbstractSpinBox">
     <enum-type name="ButtonSymbols"/>
       <modify-argument index="return">
         <replace-type modified-type="QString"/>
       </modify-argument>
+      <inject-code class="native" position="end">
+        <insert-template name="return_QString_native"/>
+      </inject-code>
       <inject-code class="target" position="end" file="../glue/qtcore.cpp" snippet="qstring-return"/>
     </modify-function>
     <modify-function signature="validate(QString &amp;,int &amp;)const">
       </modify-argument>
     </modify-function>
     <modify-function signature="setCancelButton(QPushButton*)">
-      <modify-argument index="1">
+      <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QPushButton]">
         <parent index="this" action="add"/>
       </modify-argument>
     </modify-function>
     <enum-type name="Shape"/>
     <enum-type name="SelectionBehavior"/>
     <enum-type name="ButtonPosition"/>
+    <modify-function signature="setTabButton(int,QTabBar::ButtonPosition,QWidget*)">
+      <modify-argument index="3" pyi-type="Optional[PySide6.QtWidgets.QWidget]">
+        <parent index="this" action="add"/>
+      </modify-argument>
+    </modify-function>
   </object-type>
   <object-type name="QRadioButton"/>
   <object-type name="QScrollBar"/>
         <parent index="this" action="add"/>
       </modify-argument>
     </modify-function>
+    <modify-function signature="setText(const QString&amp;)">
+        <modify-argument index="1" pyi-type="Optional[str]"/>
+    </modify-function>
     <modify-function signature="setValidator(const QValidator*)">
       <modify-argument index="1">
         <parent index="this" action="add"/>
   </object-type>
   <object-type name="QComboBox">
     <enum-type name="InsertPolicy"/>
+    <enum-type name="LabelDrawingMode" since="6.9"/>
     <enum-type name="SizeAdjustPolicy"/>
     <modify-function signature="setCompleter(QCompleter*)">
       <modify-argument index="1">
     </modify-function>
   </object-type>
   <object-type name="QGraphicsLayout">
-    <modify-function signature="getContentsMargins(qreal*,qreal*,qreal*,qreal*)const">
-        <modify-argument index="0">
-            <replace-type modified-type="PyObject"/>
-        </modify-argument>
-        <modify-argument index="1">
-            <remove-argument/>
-        </modify-argument>
-        <modify-argument index="2">
-            <remove-argument/>
-        </modify-argument>
-        <modify-argument index="3">
-            <remove-argument/>
-        </modify-argument>
-        <modify-argument index="4">
-            <remove-argument/>
-        </modify-argument>
-        <inject-code class="target" position="beginning">
-            <insert-template name="fix_number*,number*,number*,number*">
-                <replace from="$TYPE" to="qreal"/>
-            </insert-template>
-        </inject-code>
-        <inject-code class="native" position="end">
-            <insert-template name="fix_native_return_number*,number*,number*,number*">
-                <replace from="$TYPE" to="qreal"/>
-            </insert-template>
-        </inject-code>
-    </modify-function>
     <modify-function signature="widgetEvent(QEvent*)">
         <modify-argument index="1" invalidate-after-use="yes"/>
     </modify-function>
   </object-type>
   <!-- a QObject so main-thread delete redundant -->
   <object-type name="QGraphicsWidget">
-    <modify-function signature="getContentsMargins(qreal*,qreal*,qreal*,qreal*)const">
-        <modify-argument index="return">
-            <replace-type modified-type="PyObject"/>
-        </modify-argument>
-        <modify-argument index="1">
-            <remove-argument/>
-        </modify-argument>
-        <modify-argument index="2">
-            <remove-argument/>
-        </modify-argument>
-        <modify-argument index="3">
-            <remove-argument/>
-        </modify-argument>
-        <modify-argument index="4">
-            <remove-argument/>
-        </modify-argument>
-
-        <inject-code class="target" position="beginning">
-            <insert-template name="fix_number*,number*,number*,number*">
-                <replace from="$TYPE" to="qreal"/>
-            </insert-template>
-        </inject-code>
-    </modify-function>
     <modify-function signature="getWindowFrameMargins(qreal*,qreal*,qreal*,qreal*)const">
         <modify-argument index="return">
             <replace-type modified-type="PyObject"/>
     </modify-function>
   </object-type>
 
-  <object-type name="QGesture" since="4.6">
+  <object-type name="QGesture">
       <enum-type name="GestureCancelPolicy"/>
   </object-type>
-  <object-type name="QGestureRecognizer" since="4.6">
+  <object-type name="QGestureRecognizer">
       <enum-type name="ResultFlag" flags="Result"/>
       <modify-function signature="create(QObject*)">
         <modify-argument index="return">
         </modify-argument>
       </modify-function>
   </object-type>
-  <object-type name="QTapAndHoldGesture" since="4.6"/>
-  <object-type name="QTapGesture" since="4.6"/>
-  <object-type name="QGraphicsAnchor" since="4.6"/>
-  <object-type name="QGraphicsAnchorLayout" since="4.6"/>
-  <object-type name="QGraphicsBlurEffect" since="4.6">
+  <object-type name="QTapAndHoldGesture"/>
+  <object-type name="QTapGesture"/>
+  <object-type name="QGraphicsAnchor"/>
+  <object-type name="QGraphicsAnchorLayout"/>
+  <object-type name="QGraphicsBlurEffect">
       <enum-type name="BlurHint" flags="BlurHints"/>
   </object-type>
-  <object-type name="QGraphicsColorizeEffect" since="4.6"/>
-  <object-type name="QGraphicsDropShadowEffect" since="4.6"/>
+  <object-type name="QGraphicsColorizeEffect"/>
+  <object-type name="QGraphicsDropShadowEffect"/>
 
-  <object-type name="QGraphicsEffect" since="4.6">
+  <object-type name="QGraphicsEffect">
       <enum-type name="ChangeFlag" flags="ChangeFlags"/>
       <enum-type name="PixmapPadMode"/>
   </object-type>
 
-  <object-type name="QGraphicsObject" since="4.6" default-superclass="QGraphicsItem"/>
-  <object-type name="QGraphicsOpacityEffect" since="4.6"/>
-  <object-type name="QGraphicsRotation" since="4.6"/>
-  <object-type name="QGraphicsScale" since="4.6"/>
-  <object-type name="QGraphicsTransform" since="4.6"/>
-  <object-type name="QPanGesture" since="4.6"/>
-  <object-type name="QPinchGesture" since="4.6">
+  <object-type name="QGraphicsObject"/>
+  <object-type name="QGraphicsOpacityEffect"/>
+  <object-type name="QGraphicsRotation"/>
+  <object-type name="QGraphicsScale"/>
+  <object-type name="QGraphicsTransform"/>
+  <object-type name="QPanGesture"/>
+  <object-type name="QPinchGesture">
       <enum-type name="ChangeFlag" flags="ChangeFlags"/>
   </object-type>
 
       <enum-type name="TextureFormat"/>
   </object-type>
 
-  <object-type name="QSwipeGesture" since="4.6">
+  <object-type name="QSwipeGesture">
       <enum-type name="SwipeDirection"/>
   </object-type>
 
-  <value-type name="QTileRules" since="4.6"/>
+  <value-type name="QTileRules"/>
 
   <object-type name="QScroller">
       <enum-type name="State"/>
index 45c19f2e906243f32609ba30b7518a6f5692865a..197eba963eafed6fa3af499690a18b6ce50b87d1 100644 (file)
@@ -101,10 +101,13 @@ def _find_all_qt_modules():
 
     # Instead, we use __getattr__ which is supported since Python 3.7
     # and create the __all__ list on demand when needed.
-    location = Path(__file__).resolve().parent
-    files = os.listdir(location)
-    unordered = set(name[: name.find(".")] for name in files if name.startswith("Qt") and (
-                                                                name.endswith((".pyd", ".so"))))
+    unordered = set()
+    pattern = "Qt*.pyd" if sys.platform == "win32" else "Qt*.so"
+    for module in Path(__file__).resolve().parent.glob(pattern):
+        name = module.name[:module.name.find(".")]
+        if name.endswith("_d"):  # Windows debug suffix?
+            name = name[:-2]
+        unordered.add(name)
     ordered_part = __pre_all__
     result = []
     for name in ordered_part:
index 27ee0789b7fd79e12a1f013a749e94e5faa77b26..34bff7e5ec3f1435753266ca637d2c33ceaec458 100644 (file)
@@ -2,8 +2,8 @@ built_modules = list(name for name in
     "@all_module_shortnames@"
     .split(";"))
 
-shiboken_library_soversion = str(@SHIBOKEN_SO_VERSION@)
-pyside_library_soversion = str(@PYSIDE_SO_VERSION@)
+shiboken_library_soversion = "@SHIBOKEN_SO_VERSION@"
+pyside_library_soversion = "@PYSIDE_SO_VERSION@"
 
 version = "@FINAL_PACKAGE_VERSION@"
 version_info = (@BINDING_API_MAJOR_VERSION@, @BINDING_API_MINOR_VERSION@, @BINDING_API_MICRO_VERSION@, "@BINDING_API_PRE_RELEASE_VERSION_TYPE@", "@BINDING_API_PRE_RELEASE_VERSION@")
index 35ba0d1cd9ba7f7d66ada8a29c8c5002a3c9ce98..b8d551e70e7de1bd9230794c091a603a6754f4d6 100644 (file)
@@ -93,3 +93,26 @@ to a list.
     settings.value('var', type=list)  # Will get ["a"]
 
 // @snippet qsettings-value
+
+// @snippet qmessagelogger
+
+In Python, the :class:`QMessageLogger` is useful to connect an existing logging
+setup that uses the Python logging module to the Qt logging system. This allows
+you to leverage Qt's logging infrastructure while still using the familiar
+Python logging API.
+
+Example::
+
+    import logging
+    from PySide6.QtCore import QMessageLogger
+
+    class LogHandler(logging.Handler):
+        def emit(self, record: logging.LogRecord):
+            if record.levelno == logging.DEBUG:
+                logger = QMessageLogger(record.filename, record.lineno, record.funcName)
+                logger.debug(record.message)
+
+    logging.basicConfig(handlers=[LogHandler()])
+    logging.debug("Test debug message")
+
+// @snippet qmessagelogger
index 31801b2453c31f9698bc94460de6585892d69a96..ef171f80f643a5bc980dc92199263757c1c3144b 100644 (file)
@@ -139,10 +139,10 @@ It is recommended to store the QML type id, e.g. as a static member in the
 singleton class. The lookup via qmlTypeId() is costly.
 // @snippet qqmlengine-singletoninstance-qmltypeid
 
-// @snippet qqmlengine-singletoninstance-typename Returns the instance of a
-singleton type named typeName from the module specified by uri.
-For ``QObject``-derived singleton types, the ``QObject`` instance is returned,
-otherwise a ``QJSValue`` or ``None``.
+// @snippet qqmlengine-singletoninstance-typename
+Returns the instance of a singleton type named typeName from the module specified
+by uri. For ``QObject``-derived singleton types, the ``QObject`` instance is
+returned, otherwise a ``QJSValue`` or ``None``.
 
 This method can be used as an alternative to calling qmlTypeId followed by the
 id based overload of singletonInstance. This is convenient when one only needs
index c3465682c4c21ef6bba35a607915df1645a0626f..0a5019bf3bd284db4d005d8a7a71bf3a8228ce30 100644 (file)
@@ -100,7 +100,7 @@ static PyObject *settingsTypeCoercion(const QVariant &out, PyTypeObject *typeObj
         // Convert any string, etc, to a list of 1 element
         if (auto *primitiveValue = convertToPrimitiveType(out, out.typeId())) {
             PyObject *list = PyList_New(1);
-            PyList_SET_ITEM(list, 0, primitiveValue);
+            PyList_SetItem(list, 0, primitiveValue);
             return list;
         }
 
@@ -113,7 +113,7 @@ static PyObject *settingsTypeCoercion(const QVariant &out, PyTypeObject *typeObj
         PyObject *list = PyList_New(valuesSize);
         for (Py_ssize_t i = 0; i < valuesSize; ++i) {
             PyObject *item = PyUnicode_FromString(valuesList.at(i).constData());
-            PyList_SET_ITEM(list, i, item);
+            PyList_SetItem(list, i, item);
         }
         return list;
     }
@@ -433,6 +433,10 @@ static PyObject *qtmsghandler = nullptr;
 static void msgHandlerCallback(QtMsgType type, const QMessageLogContext &ctx, const QString &msg)
 {
     Shiboken::GilState state;
+    PyObject *excType{};
+    PyObject *excValue{};
+    PyObject *excTraceback{};
+    PyErr_Fetch(&excType, &excValue, &excTraceback);
     Shiboken::AutoDecRef arglist(PyTuple_New(3));
     PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QtMsgType](type));
     PyTuple_SetItem(arglist, 1, %CONVERTTOPYTHON[QMessageLogContext &](ctx));
@@ -440,6 +444,7 @@ static void msgHandlerCallback(QtMsgType type, const QMessageLogContext &ctx, co
     const char *data = array.constData();
     PyTuple_SetItem(arglist, 2, %CONVERTTOPYTHON[const char *](data));
     Shiboken::AutoDecRef ret(PyObject_CallObject(qtmsghandler, arglist));
+    PyErr_Restore(excType, excValue, excTraceback);
 }
 // @snippet qt-messagehandler
 
@@ -1292,6 +1297,18 @@ Py_BEGIN_ALLOW_THREADS
 Py_END_ALLOW_THREADS
 // @snippet qdebug-format-string
 
+// @snippet qmessagelogger-format-string
+Py_BEGIN_ALLOW_THREADS
+%CPPSELF->%FUNCTION_NAME("%s", %1); // Uses placeholder for security reasons
+Py_END_ALLOW_THREADS
+// @snippet qmessagelogger-format-string
+
+// @snippet qmessagelogger-logcategory-format-string
+Py_BEGIN_ALLOW_THREADS
+%CPPSELF->%FUNCTION_NAME(%1, "%s", %2); // Uses placeholder for security reasons
+Py_END_ALLOW_THREADS
+// @snippet qmessagelogger-logcategory-format-string
+
 // @snippet qresource-registerResource
  auto ptr = reinterpret_cast<uchar *>(Shiboken::Buffer::getPointer(%PYARG_1));
  %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(const_cast<const uchar *>(ptr), %2);
@@ -1609,6 +1626,14 @@ return PyBool_FromLong((bool)%in);
 return PyBytes_FromStringAndSize(%in.constData(), %in.size());
 // @snippet return-pybytes
 
+// @snippet chrono-to-pylong
+return PyLong_FromLong(%in.count());
+// @snippet chrono-to-pylong
+
+// @snippet pylong-to-chrono
+%out = %OUTTYPE(PyLong_AsLongLong(%in));
+// @snippet pylong-to-chrono
+
 // @snippet return-pylong
 return PyLong_FromLong(%in);
 // @snippet return-pylong
@@ -1738,18 +1763,51 @@ pthread_cleanup_pop(0);
 #endif
 // @snippet qthread_pthread_cleanup_uninstall
 
-// @snippet qlibraryinfo_build
-auto oldResult = pyResult;
-const auto version = _PepRuntimeVersion();
-pyResult = PyUnicode_FromFormat(
-#ifdef Py_LIMITED_API
-                                "%U [Python limited API %d.%d.%d]",
+// @snippet qlibraryinfo_python_build
+
+// For versions with one byte per digit.
+static QByteArray versionString(long version)
+{
+    return QByteArray::number((version >> 16) & 0xFF)
+           + '.' + QByteArray::number((version >> 8) & 0xFF)
+           + '.' + QByteArray::number(version & 0xFF);
+}
+
+static QByteArray pythonBuild()
+{
+    using namespace Qt::StringLiterals;
+
+#ifdef PYPY_VERSION
+    QByteArray result = "PyPy " PYPY_VERSION
 #else
-                                "%U [Python %d.%d.%d]",
+    QByteArray result = "Python"
+#endif
+#ifdef Py_LIMITED_API
+    " limited API"
+#endif
+#ifdef Py_GIL_DISABLED
+    " free threaded"
 #endif
-                                oldResult, (version >> 16) & 0xFF,
-                                (version >> 8) & 0xFF, version & 0xFF);
-Py_DECREF(oldResult);
+        ;
+    result += ' ';
+
+    const auto runTimeVersion = _PepRuntimeVersion();
+    const auto runTimeVersionB = versionString(runTimeVersion);
+    constexpr long buildVersion = PY_VERSION_HEX >> 8;
+    if (runTimeVersion == buildVersion) {
+        result += runTimeVersionB;
+    } else {
+        result += "run time: "_ba + runTimeVersionB + " built: "_ba
+                  + versionString(buildVersion);
+    }
+    return result;
+}
+// @snippet qlibraryinfo_python_build
+
+// @snippet qlibraryinfo_build
+QByteArray %0 = %CPPSELF.%FUNCTION_NAME();
+%0 += " [" + pythonBuild() + ']';
+%PYARG_0 = PyUnicode_FromString(%0.constData());
 // @snippet qlibraryinfo_build
 
 // @snippet qsharedmemory_data_readonly
@@ -2167,3 +2225,21 @@ QByteArray result = '<' + QByteArray(Py_TYPE(%PYSELF)->tp_name)
                     + %CPPSELF.absoluteFilePath().toUtf8() + "\")>";
 %PYARG_0 = Shiboken::String::fromCString(result.constData());
 // @snippet qdirlisting-direntry-repr
+
+// @snippet return-native-eventfilter-conversion
+%RETURN_TYPE %out = false;
+if (PySequence_Check(%PYARG_0) != 0 && PySequence_Size(%PYARG_0) == 2) {
+    Shiboken::AutoDecRef pyItem(PySequence_GetItem(%PYARG_0, 0));
+    %out = %CONVERTTOCPP[bool](pyItem);
+    if (result) {
+        Shiboken::AutoDecRef pyResultItem(PySequence_GetItem(pyResult, 1));
+        *result = %CONVERTTOCPP[qintptr](pyResultItem);
+    }
+}
+// @snippet return-native-eventfilter-conversion
+
+// @snippet return-native-eventfilter
+%PYARG_0 = PyTuple_New(2);
+PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](%0));
+PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[qintptr](*result_out));
+// @snippet return-native-eventfilter
index a91478c55439c75b30f7a177bcc90095be13ac83..72d3d2b46ee48e2ca11c11c5fff2417c7bc94943 100644 (file)
@@ -355,7 +355,7 @@ for (Py_ssize_t i = 0; i < count; ++i){
     int x, y;
     %CPPSELF.point(i, &x, &y);
     QPoint pt{x, y};
-    PyList_SET_ITEM(points, i, %CONVERTTOPYTHON[QPoint](pt));
+    PyList_SetItem(points, i, %CONVERTTOPYTHON[QPoint](pt));
 }
 // @snippet qpolygon-reduce
 
@@ -369,6 +369,29 @@ for (Py_ssize_t i = 0; i < count; ++i){
 %0 = new %TYPE(QPixmap::fromImage(%1));
 // @snippet qpixmap
 
+// @snippet qpixmap-load-xpm
+Shiboken::AutoDecRef strList(PySequence_Fast(%PYARG_1, "Invalid sequence."));
+Py_ssize_t lineCount = PySequence_Size(strList.object());
+for (Py_ssize_t line = 0; line < lineCount; ++line) {
+    Shiboken::AutoDecRef _obj(PySequence_GetItem(strList.object(), line));
+    if (!Shiboken::String::check(_obj)) {
+        PyErr_SetString(PyExc_TypeError, "The argument must be a sequence of strings.");
+        break;
+    }
+}
+// PySIDE-1735: Enums are now implemented in Python, so we need to avoid asserts.
+if (PyErr_Occurred())
+    break;
+
+Shiboken::ArrayPointer<const char*> xpm(lineCount);
+for (Py_ssize_t line = 0; line < lineCount; ++line) {
+    Shiboken::AutoDecRef _obj(PySequence_GetItem(strList.object(), line));
+    xpm[line] = Shiboken::String::toCString(_obj);
+}
+
+%0 = new %TYPE(xpm);
+// @snippet qpixmap-load-xpm
+
 // @snippet qicon-addpixmap
 const auto path = PySide::pyPathToQString(%PYARG_1);
 %CPPSELF->addPixmap(path);
@@ -384,6 +407,12 @@ const auto path = PySide::pyPathToQString(%PYARG_1);
 %CPPSELF->setImage(QImage(path));
 // @snippet qclipboard-setimage
 
+// @snippet qimage-buffer-constructor
+Py_INCREF(%PYARG_1);
+auto *ptr = reinterpret_cast<uchar *>(Shiboken::Buffer::getPointer(%PYARG_1));
+%0 = new %TYPE(ptr, %ARGS, imageDecrefDataHandler, %PYARG_1);
+// @snippet qimage-buffer-constructor
+
 // @snippet qimage-decref-image-data
 static void imageDecrefDataHandler(void *data)
 {
@@ -495,6 +524,44 @@ switch (%CPPSELF.spec()) {
 }
 // @snippet qcolor-totuple
 
+// @snippet qcolor-repr
+QString repr;
+switch (%CPPSELF.spec()) {
+case QColor::Rgb: {
+    float r, g, b, a;
+    %CPPSELF.getRgbF(&r, &g, &b, &a);
+    repr = QString::asprintf("PySide6.QtGui.QColor.fromRgbF(%.6f, %.6f, %.6f, %.6f)",
+                             r, g, b, a);
+    break;
+}
+case QColor::Hsv: {
+    float h, s, v, a;
+    %CPPSELF.getHsvF(&h, &s, &v, &a);
+    repr = QString::asprintf("PySide6.QtGui.QColor.fromHsvF(%.6f, %.6f, %.6f, %.6f)",
+                             h, s, v, a);
+    break;
+}
+case QColor::Cmyk: {
+    float c, m, y, k, a;
+    %CPPSELF.getCmykF(&c, &m, &y, &k, &a);
+    repr = QString::asprintf("PySide6.QtGui.QColor.fromCmykF(%.6f, %.6f, %.6f, %.6f, %.6f)",
+                             c, m, y, k, a);
+    break;
+}
+case QColor::Hsl: {
+    float h, s, l, a;
+    %CPPSELF.getHslF(&h, &s, &l, &a);
+    repr = QString::asprintf("PySide6.QtGui.QColor.fromHslF(%.6f, %.6f, %.6f, %.6f)",
+                             h, s, l, a);
+    break;
+}
+default:
+    repr = QLatin1StringView("PySide6.QtGui.QColor()");
+    break;
+}
+%PYARG_0 = Shiboken::String::fromCString(qPrintable(repr));
+// @snippet qcolor-repr
+
 // @snippet qcolor
 if (%1.type() == QVariant::Color)
     %0 = new %TYPE(%1.value<QColor>());
@@ -502,6 +569,55 @@ else
     PyErr_SetString(PyExc_TypeError, "QVariant must be holding a QColor");
 // @snippet qcolor
 
+// @snippet qfont-tag-from-str-helper
+using FontTagOptional = std::optional<QFont::Tag>;
+static std::optional<QFont::Tag> qFontTagFromString(PyObject *unicode)
+{
+    FontTagOptional result;
+    if (PyUnicode_GetLength(unicode) == 4)
+        result = QFont::Tag::fromString(PySide::pyUnicodeToQString(unicode));
+    if (!result.has_value())
+        PyErr_SetString(PyExc_TypeError,
+                        "QFont::Tag(): The tag name must be exactly 4 characters long.");
+    return result;
+}
+// @snippet qfont-tag-from-str-helper
+
+// @snippet qfont-tag-init-str
+const FontTagOptional tagO = qFontTagFromString(%PYARG_1);
+if (tagO.has_value())
+    %0 = new QFont::Tag(tagO.value());
+// @snippet qfont-tag-init-str
+
+// @snippet qfont-tag-fromString
+const FontTagOptional tagO = qFontTagFromString(%PYARG_1);
+if (tagO.has_value()) {
+    const auto &tag = tagO.value();
+    %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](tag);
+}
+// @snippet qfont-tag-fromString
+
+// @snippet qfont-tag-fromValue
+const FontTagOptional tagO = QFont::Tag::fromValue(PyLong_AsUnsignedLong(%PYARG_1));
+if (tagO.has_value()) {
+    const auto &tag = tagO.value();
+    %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](tag);
+} else {
+    PyErr_SetString(PyExc_TypeError, "QFont::Tag::fromValue(): Invalid value passed.");
+}
+// @snippet qfont-tag-fromValue
+
+// @snippet qfontmetrics-qfontcharfix
+if (Shiboken::String::len(%PYARG_1) == 1) {
+    const char *str = Shiboken::String::toCString(%PYARG_1);
+    const QChar ch(static_cast<unsigned short>(str[0]));
+    %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(ch);
+    %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+} else {
+    PyErr_SetString(PyExc_TypeError, "String must have only one character");
+}
+// @snippet qfontmetrics-qfontcharfix
+
 // @snippet qfontmetricsf-boundingrect
 int *array = nullptr;
 bool errorOccurred = false;
@@ -934,6 +1050,122 @@ return %CONVERTTOPYTHON[QRect](cppResult);
 %CPPSELF.%FUNCTION_NAME(%1, %2.size(), %2.constData(), %3, %4, %5);
 // @snippet qrhi-commandbuffer-setvertexinput
 
+// @snippet qpainterstateguard-restore
+%CPPSELF.restore();
+// @snippet qpainterstateguard-restore
+
+// @snippet qmatrix-repr-code
+QByteArray format(Py_TYPE(%PYSELF)->tp_name);
+format += QByteArrayLiteral("((");
+%MATRIX_TYPE data[%MATRIX_SIZE];
+%CPPSELF.copyDataTo(data);
+for (int i = 0; i < %MATRIX_SIZE; ++i) {
+    if (i > 0)
+        format += ", ";
+    format += QByteArray::number(data[i]);
+}
+format += "))";
+
+%PYARG_0 = Shiboken::String::fromStringAndSize(format, format.size());
+// @snippet qmatrix-repr-code
+
+// @snippet qmatrix-reduce-code
+%MATRIX_TYPE data[%MATRIX_SIZE];
+%CPPSELF.copyDataTo(data);
+QList<%MATRIX_TYPE> cppArgs(data, data + %MATRIX_SIZE);
+PyObject *type = PyObject_Type(%PYSELF);
+PyObject *args = Py_BuildValue("(N)",
+                               %CONVERTTOPYTHON[QList<%MATRIX_TYPE>](cppArgs));
+%PYARG_0 = Py_BuildValue("(NN)", type, args);
+// @snippet qmatrix-reduce-code
+
+// @snippet qmatrix-data-function
+PyObject *pyData = PyTuple_New(%MATRIX_SIZE);
+if (const float *data = %CPPSELF.constData()) {
+    for (int i = 0; i < %MATRIX_SIZE; ++i)
+        PyTuple_SetItem(pyData, i, %CONVERTTOPYTHON[float](data[i]));
+}
+return pyData;
+// @snippet qmatrix-data-function
+
+// @snippet qmatrix-constructor
+// PYSIDE-795: All PySequences can be made iterable with PySequence_Fast.
+Shiboken::AutoDecRef seq(PySequence_Fast(%PYARG_1, "Can't turn into sequence"));
+if (PySequence_Size(seq) == %SIZE) {
+    Shiboken::AutoDecRef fast(PySequence_Fast(seq,
+                                              "Failed to parse sequence on %TYPE constructor."));
+    float values[%SIZE];
+    for (int i = 0; i < %SIZE; ++i) {
+        Shiboken::AutoDecRef pv(PySequence_GetItem(fast.object(), i));
+        values[i] = %CONVERTTOCPP[float](pv);
+    }
+    %0 = new %TYPE(values);
+}
+// @snippet qmatrix-constructor
+
+// @snippet validator-conversionrule
+QValidator::State %out;
+
+if (PySequence_Check(%PYARG_0)) {
+    Shiboken::AutoDecRef seq(PySequence_Fast(%PYARG_0, 0));
+    const Py_ssize_t size = PySequence_Size(seq.object());
+
+    if (size > 1) {
+        Shiboken::AutoDecRef _obj1(PySequence_GetItem(seq.object(), 1));
+        if (%ISCONVERTIBLE[QString](_obj1))
+            %1 = %CONVERTTOCPP[QString](_obj1);
+        else
+            qWarning("%TYPE::%FUNCTION_NAME: Second tuple element is not convertible to unicode.");
+    }
+
+    if (size > 2) {
+        Shiboken::AutoDecRef _obj2(PySequence_GetItem(seq.object(), 2));
+        if (%ISCONVERTIBLE[int](_obj2))
+            %2 = %CONVERTTOCPP[int](_obj2);
+        else
+            qWarning("%TYPE::%FUNCTION_NAME: Second tuple element is not convertible to int.");
+    }
+    Shiboken::AutoDecRef _sobj(PySequence_GetItem(seq.object(), 0));
+
+    %PYARG_0.reset(_sobj);
+    Py_INCREF(%PYARG_0); // we need to incref, because "%PYARG_0 = ..." will decref the tuple and the tuple will be decrefed again at the end of this scope.
+}
+
+// check return value
+if (%ISCONVERTIBLE[QValidator::State](%PYARG_0)) {
+    %out = %CONVERTTOCPP[QValidator::State](%PYARG_0);
+} else {
+    PyErr_Format(PyExc_TypeError, "Invalid return value in function %s, expected %s, got %s.",
+                 "QValidator.validate",
+                 "PySide6.QtGui.QValidator.State, (PySide6.QtGui.QValidator.State,), (PySide6.QtGui.QValidator.State, unicode) or (PySide6.QtGui.QValidator.State, unicode, int)",
+                 Py_TYPE(pyResult)->tp_name);
+    return QValidator::State();
+}
+// @snippet validator-conversionrule
+
+// @snippet fix_margins_return
+PyObject *obj = %PYARG_0.object();
+bool ok = false;
+if (PySequence_Check(obj) != 0 && PySequence_Size(obj) == 4) {
+    Shiboken::AutoDecRef m0(PySequence_GetItem(obj, 0));
+    Shiboken::AutoDecRef m1(PySequence_GetItem(obj, 1));
+    Shiboken::AutoDecRef m2(PySequence_GetItem(obj, 2));
+    Shiboken::AutoDecRef m3(PySequence_GetItem(obj, 3));
+    ok = PyNumber_Check(m0) != 0 && PyNumber_Check(m1) != 0
+         && PyNumber_Check(m2) && PyNumber_Check(m3) != 0;
+    if (ok) {
+        *%1 = %CONVERTTOCPP[$TYPE](m0);
+        *%2 = %CONVERTTOCPP[$TYPE](m1);
+        *%3 = %CONVERTTOCPP[$TYPE](m2);
+        *%4 = %CONVERTTOCPP[$TYPE](m3);
+    }
+}
+if (!ok) {
+    PyErr_SetString(PyExc_TypeError, "Sequence of 4 numbers expected");
+    %1 = %2 = %3 = %4 = 0;
+}
+// @snippet fix_margins_return
+
 /*********************************************************************
  * CONVERSIONS
  ********************************************************************/
index 7b1c2d5646de3ac9eac5bc1750200b2080e0a54e..9a4e28ce22227d98db9677a707b93b11397704ef 100644 (file)
@@ -2,7 +2,7 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 // @snippet qudpsocket-readdatagram
-Shiboken::AutoArrayPointer<char> data(%ARGUMENT_NAMES);
+Shiboken::ArrayPointer<char> data(%ARGUMENT_NAMES);
 QHostAddress ha;
 quint16 port;
 %BEGIN_ALLOW_THREADS
index 060418fafb26ef64737c122b81a99d2a653fe6fa..8691af07ae0af05373d46905904373030c852443 100644 (file)
@@ -11,7 +11,7 @@ const Py_ssize_t vertexCount = %CPPSELF->vertexCount();
 %PYARG_0 = PyList_New(vertexCount);
 for (Py_ssize_t i = 0; i < vertexCount; ++i) {
     QSGGeometry::Point2D p = points[i];
-    PyList_SET_ITEM(%PYARG_0, i, %CONVERTTOPYTHON[QSGGeometry::Point2D](p));
+    PyList_SetItem(%PYARG_0, i, %CONVERTTOPYTHON[QSGGeometry::Point2D](p));
 }
 // @snippet qsgeometry-vertexdataaspoint2d
 
diff --git a/sources/pyside6/PySide6/glue/qtremoteobjects.cpp b/sources/pyside6/PySide6/glue/qtremoteobjects.cpp
new file mode 100644 (file)
index 0000000..88d5858
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+// @snippet qtro-init
+PySide::RemoteObjects::init(module);
+// @snippet qtro-init
+
+// @snippet node-acquire
+auto *typeObject = reinterpret_cast<PyTypeObject*>(%PYARG_1);
+if (!PySide::inherits(typeObject, SbkPySide6_QtRemoteObjectsTypeStructs[SBK_QRemoteObjectReplica_IDX].fullName)) {
+    PyErr_SetString(PyExc_TypeError, "First argument must be a type deriving from QRemoteObjectReplica.");
+    return nullptr;
+}
+
+static PyObject *pyConstructWithNode = Shiboken::Enum::newItem(
+    Shiboken::Module::get(SbkPySide6_QtRemoteObjectsTypeStructs[SBK_QRemoteObjectReplica_ConstructorType_IDX]),
+    1 /* protected QRemoteObjectReplica::ConstructorType::ConstructWithNode */
+);
+
+Shiboken::AutoDecRef args;
+if (pyArgs[1])
+    args.reset(PyTuple_Pack(3, %PYSELF, pyConstructWithNode, pyArgs[1]));
+else
+    args.reset(PyTuple_Pack(2, %PYSELF, pyConstructWithNode));
+
+PyObject *instance = PyObject_CallObject(%PYARG_1, args.object());
+if (!instance)
+    return nullptr;  // Propagate the exception
+
+%PYARG_0 = instance;
+// @snippet node-acquire
index 36d3bfa37b0fe09ba2482b0a435a496804892454..6d6336a83ab3da208ff224e7f60a0d0df52ff644 100644 (file)
@@ -20,5 +20,11 @@ if (emitter == nullptr || signature.isEmpty()) {
     PyErr_SetString(PyExc_ValueError, error.constData());
     return -1;
 }
+
+// PySide::Signal::getObject() increments the refcount for emitterPyObject,
+// but there is nothing that decrements the count when the spy goes out of
+// scope.  It doesn't seem like QSignalSpy should prevent the target object
+// from being garbage collected.  So we need to decrement the refcount here.
+Py_DECREF(emitterPyObject);
 %0 = new QSignalSpy(emitter, signature.constData());
 // @snippet qsignalspy-signal
index bc9dc99d62cb5614bc917b09debb25d9d5d4c6cb..c085cb0621b23ee9cbb710fdb2a2b386287bbbd1 100644 (file)
@@ -815,6 +815,16 @@ if (!PySide::callConnect(%PYSELF, SIGNAL(accepted()), %PYARG_1))
 %CPPSELF.%FUNCTION_NAME();
 // @snippet qmessagebox-open-connect-accept
 
+// @snippet replace-widget-child
+$CHILD_TYPE* oldChild = %CPPSELF.$FUNCTION_GET_OLD();
+if (oldChild != nullptr && oldChild != $CPPARG) {
+    Shiboken::AutoDecRef pyChild(%CONVERTTOPYTHON[$CHILD_TYPE*](oldChild));
+    Shiboken::Object::setParent(nullptr, pyChild);
+    Shiboken::Object::releaseOwnership(pyChild);
+}
+Shiboken::Object::setParent(%PYSELF, $PYARG);
+// @snippet replace-widget-child
+
 /*********************************************************************
  * CONVERSIONS
  ********************************************************************/
index aece7eaa9c1b68d460773aea4aac90e1c9cf894a..663a0a5cb5ac5e835fd0ffe5a55f9d1467af417e 100644 (file)
     <template name="pybytes_uint">
           uint %out = static_cast&lt;uint>(PyBytes_Size(%PYARG_1));
     </template>
+
+    <template name="pysequencesize_int">
+        Py_ssize_t %out = PySequence_Size(%PYARG_1);
+    </template>
+
+    <!-- Convert an indexable C-style arrray %TYPE[%COUNT] to PySequence -->
+    <template name="c-array-to-pysequence">
+        auto *object = PyList_New(0);
+        for (int i = 0; i &lt; %COUNT; i++) {
+            PyList_Append(object, %CONVERTTOPYTHON[%TYPE](%in[i]));
+        }
+        PyObject *%out = object;
+    </template>
+
+    <!-- Convert a PySequence to an indexable C-style arrray %TYPE[] via ArrayPointer -->
+    <template name="pysequence-to-c-array">
+        const Py_ssize_t count = PySequence_Size(%PYARG_1);
+        Shiboken::ArrayPointer&lt;%TYPE&gt; %out(count);
+        for (Py_ssize_t i = 0; i &lt; count; ++i) {
+            Shiboken::AutoDecRef a(PySequence_GetItem(%PYARG_1, i));
+            %out[i] = %CONVERTTOCPP[%TYPE](a);
+        }
+    </template>
+
 </typesystem>
index c794e7b8f780e94674762a0db183f97760a4c98a..5149f1a79794d0bb047e09998eecab43c66d4b11 100644 (file)
         <insert-template name="tuple_retval_ok"/>
     </template>
 
-    <template name="fix_bool*,arg,arg">
-        bool ok_;
-        %RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(&amp;ok_, %2, %3);
-        <insert-template name="tuple_retval_ok"/>
-    </template>
-
-    <template name="fix_bool*,arg,arg,arg">
-        bool ok_;
-        %RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(&amp;ok_, %2, %3, %4);
-        <insert-template name="tuple_retval_ok"/>
-    </template>
-
-    <template name="fix_bool*,arg,arg,arg,arg">
-        bool ok_;
-        %RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(&amp;ok_, %2, %3, %4, %5);
-        <insert-template name="tuple_retval_ok"/>
-    </template>
-
     <!-- QInputDialog: these should allow threads -->
     <template name="fix_arg,arg,arg,arg,arg,arg,arg,bool*,arg">
         bool ok_;
         <insert-template name="tuple_abcd_same_type"/>
     </template>
 
-    <template name="fix_number*,number*,number*,number*,args">
-        $TYPE a, b, c, d;
-        %CPPSELF->::%TYPE::%FUNCTION_NAME(&amp;a, &amp;b, &amp;c, &amp;d, %ARGUMENT_NAMES);
-        <insert-template name="tuple_abcd_same_type"/>
-    </template>
-
-    <template name="fix_native_return_number*,number*,number*,number*">
-        PyObject* _obj = %PYARG_0.object();
-        Shiboken::AutoDecRef _obj0(PySequence_GetItem(_obj, 0));
-        Shiboken::AutoDecRef _obj1(PySequence_GetItem(_obj, 1));
-        Shiboken::AutoDecRef _obj2(PySequence_GetItem(_obj, 2));
-        Shiboken::AutoDecRef _obj3(PySequence_GetItem(_obj, 3));
-        if (!PySequence_Check(_obj)
-            || PySequence_Size(_obj) != 4
-            || !PyNumber_Check(_obj0)
-            || !PyNumber_Check(_obj1)
-            || !PyNumber_Check(_obj2)
-            || !PyNumber_Check(_obj3)) {
-            PyErr_SetString(PyExc_TypeError, "Sequence of 4 numbers expected");
-        } else {
-            *%1 = %CONVERTTOCPP[$TYPE](_obj0);
-            *%2 = %CONVERTTOCPP[$TYPE](_obj1);
-            *%3 = %CONVERTTOCPP[$TYPE](_obj2);
-            *%4 = %CONVERTTOCPP[$TYPE](_obj3);
-        }
-    </template>
-
     <template name="fix_number*,number*,number*,number*,number*">
         $TYPE a, b, c, d, e;
         %CPPSELF.%FUNCTION_NAME(&amp;a, &amp;b, &amp;c, &amp;d, &amp;e);
         PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[$TYPE](b));
     </template>
 
-    <template name="fix_arg,int*,int*">
-    %RETURN_TYPE _ret;
-    int a, b;
-    _ret = %CPPSELF.%FUNCTION_NAME(%1, &amp;a, &amp;b);
-    %PYARG_0 = PyTuple_New(3);
-    PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](_ret));
-    PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[int](a));
-    PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[int](b));
-    </template>
-
     <template name="return_tuple_QValidator_QString_int">
         %RETURN_TYPE retval_ = %RETURN_TYPE(%CPPSELF.%FUNCTION_NAME(%1, %2));
         %PYARG_0 = PyTuple_New(3);
         Py_INCREF(%PYARG_0);
     </template>
 
-    <!-- Helpers for modifying "bool nativeEventFilter(QByteArray, void*, long *result)"
-         to return a tuple of bool,long -->
+    <!-- Helpers for modifying "bool nativeEventFilter(QByteArray, void*, qintptr *result)"
+         to return a tuple of bool,qintptr -->
     <template name="return_native_eventfilter_conversion_variables">
         qintptr resultVar{0};
         qintptr *%out = &amp;resultVar;
     </template>
-    <template name="return_native_eventfilter_conversion">
-        %RETURN_TYPE %out = false;
-        if (PySequence_Check(%PYARG_0) &amp;&amp; (PySequence_Size(%PYARG_0) == 2)) {
-            Shiboken::AutoDecRef pyItem(PySequence_GetItem(%PYARG_0, 0));
-            %out = %CONVERTTOCPP[bool](pyItem);
-            if (result) {
-                Shiboken::AutoDecRef pyResultItem(PySequence_GetItem(pyResult, 1));
-                *result = %CONVERTTOCPP[long](pyResultItem);
-            }
-        }
-    </template>
-
-    <template name="return_native_eventfilter">
-        %PYARG_0 = PyTuple_New(2);
-        PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](%0));
-        PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[long](*result_out));
-    </template>
+    <template name="return_native_eventfilter_conversion"
+              file="../glue/qtcore.cpp" snippet="return-native-eventfilter-conversion"/>
 
     <!-- templates for __reduce__ -->
     <template name="reduce_code">
index 6d5861322e20d17f6df23b71112540e12c780929..98cf79bd9579f67a468febb2ad29c2faace1f5be 100644 (file)
@@ -13,9 +13,9 @@
             PyObject *pyRow = PyList_New(columnCount);
             for (Py_ssize_t c = 0; c &lt; columnCount; ++c) {
                 const %INTYPE_0 &amp;cppItem = row->at(c);
-                PyList_SET_ITEM(pyRow, c, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
+                PyList_SetItem(pyRow, c, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
             }
-            PyList_SET_ITEM(%out, r, pyRow);
+            PyList_SetItem(%out, r, pyRow);
         }
         return %out;
     </template>
index e21d356d7510f33e2af99a3a09438717027f3940..032fc9779fa19f0ce7adff438ee186aa326367ae 100644 (file)
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 -->
 <typesystem>
-    <template name="QFontCharFix">
-       int size = Shiboken::String::len(%PYARG_1);
-       if (size == 1) {
-           const char *str = Shiboken::String::toCString(%PYARG_1);
-           const QChar ch(static_cast&lt;unsigned short&gt;(str[0]));
-           %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(ch);
-           %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
-       } else {
-           PyErr_SetString(PyExc_TypeError, "String must have only one character");
-       }
-    </template>
-
-    <template name="load_xpm">
-        Shiboken::AutoDecRef strList(PySequence_Fast(%PYARG_1, "Invalid sequence."));
-        Py_ssize_t lineCount = PySequence_Size(strList.object());
-        for (Py_ssize_t line = 0; line &lt; lineCount; ++line) {
-            Shiboken::AutoDecRef _obj(PySequence_GetItem(strList.object(), line));
-            bool isString = Shiboken::String::check(_obj);
-            if (!isString) {
-                PyErr_SetString(PyExc_TypeError, "The argument must be a sequence of strings.");
-                break;
-            }
-        }
-        // PySIDE-1735: Enums are now implemented in Python, so we need to avoid asserts.
-        if (PyErr_Occurred())
-            break;
-
-        auto xpm = new const char*[lineCount];
-        for (Py_ssize_t line = 0; line &lt; lineCount; ++line) {
-            Shiboken::AutoDecRef _obj(PySequence_GetItem(strList.object(), line));
-            xpm[line] = Shiboken::String::toCString(_obj);
-        }
 
-        %0 = new %TYPE(xpm);
+    <template name="qimage_buffer_constructor"
+              file="../glue/qtgui.cpp" snippet="qimage-buffer-constructor"/>
 
-        delete [] xpm;
-    </template>
-
-    <template name="qmatrix_map">
-        %ARG1_TYPE a, b;
-        %CPPSELF.%FUNCTION_NAME(%1, %2, &amp;a, &amp;b);
-        %PYARG_0 = PyTuple_New(2);
-        PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%ARG1_TYPE](a));
-        PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[%ARG1_TYPE](b));
-    </template>
-
-    <template name="qimage_buffer_constructor">
-        Py_INCREF(%PYARG_1);
-        auto ptr = reinterpret_cast&lt;uchar*&gt;(Shiboken::Buffer::getPointer(%PYARG_1));
-        %0 = new %TYPE(ptr, %ARGS, imageDecrefDataHandler, %PYARG_1);
-    </template>
-
-    <template name="qcolor_repr">
-        switch(%CPPSELF.spec()) {
-          case QColor::Rgb:
-          {
-              float r, g, b, a;
-              %CPPSELF.getRgbF(&amp;r, &amp;g, &amp;b, &amp;a);
-              QString repr = QString::asprintf("PySide6.QtGui.QColor.fromRgbF(%.6f, %.6f, %.6f, %.6f)", r, g, b, a);
-              %PYARG_0 = Shiboken::String::fromCString(qPrintable(repr));
-              break;
-          }
-          case QColor::Hsv:
-          {
-              float h, s, v, a;
-              %CPPSELF.getHsvF(&amp;h, &amp;s, &amp;v, &amp;a);
-              QString repr = QString::asprintf("PySide6.QtGui.QColor.fromHsvF(%.6f, %.6f, %.6f, %.6f)", h, s, v, a);
-              %PYARG_0 = Shiboken::String::fromCString(qPrintable(repr));
-              break;
-          }
-          case QColor::Cmyk:
-          {
-              float c, m, y, k, a;
-              %CPPSELF.getCmykF(&amp;c, &amp;m, &amp;y, &amp;k, &amp;a);
-              QString repr = QString::asprintf("PySide6.QtGui.QColor.fromCmykF(%.6f, %.6f, %.6f, %.6f, %.6f)", c, m, y, k, a);
-              %PYARG_0 = Shiboken::String::fromCString(qPrintable(repr));
-              break;
-          }
-          case QColor::Hsl:
-          {
-              float h, s, l, a;
-              %CPPSELF.getHslF(&amp;h, &amp;s, &amp;l, &amp;a);
-              QString repr = QString::asprintf("PySide6.QtGui.QColor.fromHslF(%.6f, %.6f, %.6f, %.6f)", h, s, l, a);
-              %PYARG_0 = Shiboken::String::fromCString(qPrintable(repr));
-              break;
-          }
-          default:
-          {
-              %PYARG_0 = Shiboken::String::fromCString("PySide6.QtGui.QColor()");
-          }
-       }
-    </template>
-
-    <template name="validator_conversionrule">
-          QValidator::State %out;
-
-          if (PySequence_Check(%PYARG_0)) {
-              Shiboken::AutoDecRef seq(PySequence_Fast(%PYARG_0, 0));
-              const Py_ssize_t size = PySequence_Size(seq.object());
-
-              if (size > 1) {
-                  Shiboken::AutoDecRef _obj1(PySequence_GetItem(seq.object(), 1));
-                  if (%ISCONVERTIBLE[QString](_obj1))
-                      %1 = %CONVERTTOCPP[QString](_obj1);
-                  else
-                      qWarning("%TYPE::%FUNCTION_NAME: Second tuple element is not convertible to unicode.");
-              }
-
-              if (size > 2) {
-                  Shiboken::AutoDecRef _obj2(PySequence_GetItem(seq.object(), 2));
-                  if (%ISCONVERTIBLE[int](_obj2))
-                      %2 = %CONVERTTOCPP[int](_obj2);
-                  else
-                      qWarning("%TYPE::%FUNCTION_NAME: Second tuple element is not convertible to int.");
-              }
-              Shiboken::AutoDecRef _sobj(PySequence_GetItem(seq.object(), 0));
-
-              %PYARG_0.reset(_sobj);
-              Py_INCREF(%PYARG_0); // we need to incref, because "%PYARG_0 = ..." will decref the tuple and the tuple will be decrefed again at the end of this scope.
-          }
-
-          // check retrun value
-          if (%ISCONVERTIBLE[QValidator::State](%PYARG_0)) {
-              %out = %CONVERTTOCPP[QValidator::State](%PYARG_0);
-          } else {
-              PyErr_Format(PyExc_TypeError, "Invalid return value in function %s, expected %s, got %s.",
-                          "QValidator.validate",
-                          "PySide6.QtGui.QValidator.State, (PySide6.QtGui.QValidator.State,), (PySide6.QtGui.QValidator.State, unicode) or (PySide6.QtGui.QValidator.State, unicode, int)",
-                          Py_TYPE(pyResult)->tp_name);
-              return QValidator::State();
-          }
-    </template>
+    <template name="validator_conversionrule"
+              file="../glue/qtgui.cpp" snippet="validator-conversionrule"/>
 
     <template name="qpainter_drawlist">
         %CPPSELF.%FUNCTION_NAME(%1.constData(), %1.size());
         return %CONVERTTOPYTHON[%RETURN_TYPE](*%CPPSELF);
     </template>
 
-    <template name="inplace_sub">
-        *%CPPSELF -= %1;
-        return %CONVERTTOPYTHON[%RETURN_TYPE](*%CPPSELF);
-    </template>
-
-    <template name="inplace_mult">
-        *%CPPSELF *= %1;
-        return %CONVERTTOPYTHON[%RETURN_TYPE](*%CPPSELF);
-    </template>
-
-    <template name="inplace_div">
-        *%CPPSELF /= %1;
-        return %CONVERTTOPYTHON[%RETURN_TYPE](*%CPPSELF);
-    </template>
-
     <template name="return_QString_native">
         if (%ISCONVERTIBLE[QString](%PYARG_0))
             %1 = %CONVERTTOCPP[QString](%PYARG_0);
             qWarning("%TYPE::%FUNCTION_NAME: Argument is not convertible to unicode.");
     </template>
 
-    <template name="repr_code_matrix">
-        QByteArray format(Py_TYPE(%PYSELF)->tp_name);
-        format += QByteArrayLiteral("((");
+    <template name="repr_code_matrix"
+              file="../glue/qtgui.cpp" snippet="qmatrix-repr-code"/>
 
-        QList&lt; %MATRIX_TYPE &gt; cppArgs;
-        %MATRIX_TYPE data[%MATRIX_SIZE];
-        %CPPSELF.copyDataTo(data);
-        int matrixSize = %MATRIX_SIZE;
-        for(int size=0; size &lt; matrixSize; size++) {
-            if (size > 0)
-                format += ", ";
-            format += QByteArray::number(data[size]);
-        }
-        format += "))";
+    <template name="reduce_code_matrix"
+              file="../glue/qtgui.cpp" snippet="qmatrix-reduce-code"/>
 
-        %PYARG_0 = Shiboken::String::fromStringAndSize(format, format.size());
-    </template>
-
-    <template name="reduce_code_matrix">
-        QList&lt; %MATRIX_TYPE &gt; cppArgs;
-        %MATRIX_TYPE data[%MATRIX_SIZE];
-        %CPPSELF.copyDataTo(data);
-        int matrixSize = %MATRIX_SIZE;
-        for(int size=0; size &lt; matrixSize; size++)
-            cppArgs.append(data[size]);
+    <template name="matrix_data_function"
+              file="../glue/qtgui.cpp" snippet="qmatrix-data-function"/>
 
-        PyObject *type = PyObject_Type(%PYSELF);
-        PyObject *args = Py_BuildValue("(N)",
-            %CONVERTTOPYTHON[QList&lt;%MATRIX_TYPE&gt; ](cppArgs));
-        %PYARG_0 = Py_BuildValue("(NN)", type, args);
-    </template>
-
-    <template name="matrix_data_function">
-        const float* data = %CPPSELF.constData();
-        PyObject *pyData = PyTuple_New(%MATRIX_SIZE);
-        if (data) {
-            for(int i=0; i &lt; %MATRIX_SIZE; i++)
-                PyTuple_SetItem(pyData, i, %CONVERTTOPYTHON[float](data[i]));
-        }
-        return pyData;
-    </template>
-
-    <template name="matrix_constructor">
-        // PYSIDE-795: All PySequences can be made iterable with PySequence_Fast.
-        Shiboken::AutoDecRef seq(PySequence_Fast(%PYARG_1, "Can't turn into sequence"));
-        if (PySequence_Size(seq) == %SIZE) {
-            Shiboken::AutoDecRef fast(PySequence_Fast(seq,
-                "Failed to parse sequence on %TYPE constructor."));
-            float values[%SIZE];
-            for(int i=0; i &lt; %SIZE; i++) {
-                Shiboken::AutoDecRef pv(PySequence_GetItem(fast.object(), i));
-                values[i] = %CONVERTTOCPP[float](pv);
-            }
-            %0 = new %TYPE(values);
-        }
-    </template>
+    <template name="matrix_constructor"
+              file="../glue/qtgui.cpp" snippet="qmatrix-constructor"/>
 
     <template name="fix_args,QRectF*">
         QRectF rect_;
           uint %out = static_cast&lt;uint>(PyBytes_Size(%PYARG_1));
     </template>
 
+    <template name="fix_native_return_number*,number*,number*,number*"
+              file="../glue/qtgui.cpp" snippet="fix_margins_return"/>
 
 </typesystem>
diff --git a/sources/pyside6/PySide6/templates/opengl_common.xml b/sources/pyside6/PySide6/templates/opengl_common.xml
deleted file mode 100644 (file)
index 3435ef8..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
--->
-<typesystem>
-    <template name="callArrayFunction">
-        Py_ssize_t _size = PySequence_Size(%PYARG_2);
-        if (_size) {
-            $ATTR_TYPE *_list = new $ATTR_TYPE[_size];
-            if (_size) {
-              Shiboken::AutoDecRef fast(PySequence_Fast(%PYARG_2,
-                "Failed to parse sequence with type %VECTOR_TYPE."));
-              for(Py_ssize_t i=0; i &lt; _size; i++) {
-                  Shiboken::AutoDecRef pv(PySequence_GetItem(fast.object(), i));
-                  _list[i] = %CONVERTTOCPP[$ATTR_TYPE](pv);
-              }
-            }
-            %CPPSELF.%FUNCTION_NAME(%1, _list, $ARG0);
-            delete[] _list;
-         } else {
-            %CPPSELF.%FUNCTION_NAME(%1, reinterpret_cast&lt;$ATTR_TYPE*&gt;(nullptr), $ARG1);
-         }
-    </template>
-    <template name="glGetString_return_QString">
-        const GLubyte *us = %CPPSELF.%FUNCTION_NAME(%ARGUMENT_NAMES);
-        const QString s = QString::fromLocal8Bit(reinterpret_cast&lt;const char *&gt;(us));
-        %PYARG_0 = %CONVERTTOPYTHON[QString](s);
-    </template>
-</typesystem>
index 39dd80cf8f8e57c9cafc543c2b847100d91463bd..4bd8e9168f3c6d945a0f2c94be06004ac1254dd8 100644 (file)
@@ -5,33 +5,10 @@
 -->
 <typesystem>
 
-    <template name="replace_child">
-        $CHILD_TYPE* oldChild = %CPPSELF.$FUNCTION_GET_OLD();
-        if (oldChild &amp;&amp; (oldChild != $CPPARG)) {
-            Shiboken::AutoDecRef pyChild(%CONVERTTOPYTHON[$CHILD_TYPE*](oldChild));
-            Shiboken::Object::setParent(nullptr, pyChild);
-            Shiboken::Object::releaseOwnership(pyChild);
-        }
-        Shiboken::Object::setParent(%PYSELF, $PYARG);
-    </template>
-
-    <template name="qgraphicsitem_pysequence">
-        const Py_ssize_t numItems = PySequence_Size(%PYARG_1);
-        Shiboken::AutoArrayPointer&lt;QGraphicsItem*&gt; %out(numItems);
-        for (Py_ssize_t i = 0; i &lt; numItems; ++i) {
-            Shiboken::AutoDecRef _arg1(PySequence_GetItem(%PYARG_1, i));
-            %out[i] = %CONVERTTOCPP[QGraphicsItem*](_arg1);
-        }
-    </template>
-
-    <template name="qgraphicsitem_pyobject">
-        Shiboken::AutoDecRef object(PyList_New(0));
-        for (int i=0, max=numItems; i &lt; max; i++) {
-            PyList_Append(object, %CONVERTTOPYTHON[QGraphicsItem*](%in[i]));
-        }
-        PyObject *%out = object.object();
-    </template>
+    <template name="replace_child"
+              file="../glue/qtwidgets.cpp" snippet="replace-widget-child"/>
 
+    <!-- Do pointer conversion here since QStyleOptionGraphicsItem is an "object-type". -->
     <template name="qstyleoptiongraphicsitem_pyobject">
         Shiboken::AutoDecRef option_object(PyList_New(0));
         for (int i=0, max=numItems; i &lt; max; i++) {
 
     <template name="pysequence_qstyleoptiongraphicsitem">
         const Py_ssize_t numOptions = PySequence_Size(%PYARG_2);
-        Shiboken::AutoArrayPointer&lt;QStyleOptionGraphicsItem&gt; %out(numOptions);
+        Shiboken::ArrayPointer&lt;QStyleOptionGraphicsItem&gt; %out(numOptions);
         for (Py_ssize_t i=0; i &lt; numOptions; ++i) {
             Shiboken::AutoDecRef _arg1(PySequence_GetItem(%PYARG_1, i));
             %out[i] = %CONVERTTOCPP[QStyleOptionGraphicsItem](_arg1);
         }
     </template>
 
-    <template name="pysequencesize_int">
-        Py_ssize_t %out = PySequence_Size(%PYARG_1);
-    </template>
-
 </typesystem>
index a83f2b7453963fcc43edca019300ce02e9159cc2..5cd12b6832d29aeb40b73a61c86f1652dcae1086 100644 (file)
@@ -297,6 +297,7 @@ macro(create_pyside_module)
     set(ld_prefix_list "")
     list(APPEND ld_prefix_list "${pysidebindings_BINARY_DIR}/libpyside")
     list(APPEND ld_prefix_list "${pysidebindings_BINARY_DIR}/libpysideqml")
+    list(APPEND ld_prefix_list "${pysidebindings_BINARY_DIR}/libpysideremoteobjects")
     list(APPEND ld_prefix_list "${SHIBOKEN_SHARED_LIBRARY_DIR}")
     if(WIN32)
         list(APPEND ld_prefix_list "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_BINS}")
index ad94f278fe8d80c632b8610cd046b61ef3bdc4c6..a4641ee651616ef280ee7d4128095ac57d13605a 100644 (file)
@@ -173,13 +173,6 @@ macro(use_protected_as_public_hack)
 endmacro()
 
 macro(remove_skipped_modules)
-    # Removing from the MODULES list the items that were defined with
-    # -DSKIP_MODULES on command line
-    if(SKIP_MODULES)
-        foreach(s ${SKIP_MODULES})
-            list(REMOVE_ITEM MODULES ${s})
-        endforeach()
-    endif()
 
     foreach(m ${MODULES})
         collect_module_if_found(${m})
@@ -221,7 +214,6 @@ macro(collect_module_if_found shortname)
         set(quiet_argument "QUIET")
     endif()
 
-    find_package(${_qt_module_name} ${quiet_argument})
     # If package is found, _name_found will be equal to 1
     set(_name_found "${_qt_module_name}_FOUND")
     # _name_dir will keep the path to the directory where the CMake rules were found
index 34ec035b71d175c3c2e1f5391a9c82a6b66f8af3..45a63a1a097f1eb682f1f7d90c64f0ed38d4e44e 100644 (file)
@@ -170,7 +170,20 @@ collect_optional_modules()
 # Modules to be built unless specified by -DMODULES on command line
 if(NOT MODULES)
     set(MODULES "${ALL_ESSENTIAL_MODULES};${ALL_OPTIONAL_MODULES}")
+    set(required_modules ${ALL_ESSENTIAL_MODULES})
+    set(optional_modules ${ALL_OPTIONAL_MODULES})
+else()
+    set(required_modules ${MODULES})
+    set(optional_modules)
 endif()
+list(REMOVE_ITEM MODULES ${SKIP_MODULES})
+list(REMOVE_ITEM required_modules ${SKIP_MODULES})
+list(REMOVE_ITEM optional_modules ${SKIP_MODULES})
+
+find_package(Qt6
+    COMPONENTS ${required_modules}
+    OPTIONAL_COMPONENTS ${optional_modules}
+)
 
 # This will contain the set of modules for which bindings are not built.
 set(DISABLED_MODULES "${ALL_ESSENTIAL_MODULES};${ALL_OPTIONAL_MODULES}")
@@ -186,6 +199,9 @@ endforeach()
 # Whether to add libpysideqml
 find_package(Qt6 COMPONENTS Qml)
 
+# Whether to add libpysideremoteobjects
+find_package(Qt6 COMPONENTS RemoteObjects)
+
 string(REGEX MATCHALL "[0-9]+" qt_version_helper "${Qt${QT_MAJOR_VERSION}Core_VERSION}")
 
 list(GET qt_version_helper 0 QT_VERSION_MAJOR)
index 8e64720bfa4982199051571875473343595ba423..1e59006e20663a08738e8beec291279fce467c16 100644 (file)
@@ -121,7 +121,7 @@ Assuming that Qt is in PATH, for example, the configure step can be done with::
           -DPython_EXECUTABLE=/path/to/interpreter
 
 .. note:: You can add `-DFORCE_LIMITED_API=yes` in case you want to have a
-   build which will be compatible with Python 3.8+.
+   build which will be compatible with Python 3.9+.
 
 and then for building::
 
index dca804cba653cfb842fc98e6fceac188f6fed1a3..67a3d48ed0196c4eebf60558ace38408a8689e85 100644 (file)
@@ -74,7 +74,7 @@ For building the documentation::
 
 .. note:: Keep in mind you need to use the same version as your Qt installation
 
-.. note:: With `uv`_, use `uv pip install ...
+.. note:: With `uv`_, use ``uv pip install ...``
 
 Building PySide
 ~~~~~~~~~~~~~~~
index d2510a1b6838db7340caa821544b44a62aef43f5..737d045b345897e1954bd59d015ed280db095ba1 100644 (file)
@@ -19,8 +19,14 @@ Requirements
 .. _OpenSSL: https://sourceforge.net/projects/openssl/
 .. _`Qt for Windows`: https://doc.qt.io/qt-6/windows.html
 
-Building from source on Windows 10
-----------------------------------
+Building from source on Windows
+-------------------------------
+
+Creating a Dev Drive
+~~~~~~~~~~~~~~~~~~~~
+
+We recommend using a `Dev Drive`_ for development work on Windows. This is a
+special partition with a fast file system that is excluded from virus scanning.
 
 Creating a virtual environment
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -143,3 +149,4 @@ Remember to properly set the environment variables for Qt and PySide::
     python examples\widgets\widgets\tetrix\tetrix.py
 
 .. _`uv`: https://docs.astral.sh/uv/
+.. _`Dev Drive`: https://learn.microsoft.com/en-us/windows/dev-drive/
index 534d07072c377bfefff0400844a0225792cc8d53..d71018bf929248b1c594abc14317ce1ce92892c6 100644 (file)
@@ -10,7 +10,7 @@ compiles your Python code to C code, and links with libpython to produce the fin
 The final executable produced has a ``.exe`` suffix on Windows, ``.bin`` on Linux and ``.app`` on
 macOS.
 
-.. note:: The default version of Nuitka used with the tool is version ``2.5.1``. This can be
+.. note:: The default version of Nuitka used with the tool is version ``2.7.11``. This can be
     updated to a newer version by updating your ``pysidedeploy.spec`` file.
 
 .. _how_pysidedeploy:
@@ -88,7 +88,7 @@ The relevant parameters for ``pyside6-deploy`` are:
     is the parent directory of the main Python entry point file
   * ``input_file``: Path to the main Python entry point file
   * ``project_file``: If it exists, this points to the path to the `Qt Creator Python Project File
-    .pyproject <https://doc.qt.io/qtforpython-6/faq/typesoffiles.html
+    <https://doc.qt.io/qtforpython-6/faq/typesoffiles.html
     #qt-creator-python-project-file-pyproject>`_ file. Such a file makes sure that the deployment
     process never considers unnecessary files when bundling the executable.
   * ``exec_directory``: The directory where the final executable is generated.
index 18f826bc3334c09d4e1d15c67d5d82b14cb800fd..59aa2f115a43df960eb4413da58cc5a09632931a 100644 (file)
@@ -60,6 +60,7 @@ Port a Qt example
   ``snippets-translate``).
 - Note that our examples need to have unique names due to the doc build.
 - Verify that all slots are decorated using ``@Slot``.
+- Enumerations should be fully qualified (PYSIDE-1735).
 - Add a ``.pyproject`` file (verify later on that docs build).
 - Add a ``doc`` directory and descriptive ``.rst`` file,
   and a screenshot if suitable (use ``optipng`` to reduce file size).
index 9788b539d687a2d2ed7cba9acd35e5560431116b..1cccaad392420d74781acecd6094d395cd07d5f0 100644 (file)
@@ -22,14 +22,15 @@ To test the wheels:
 Build on the command line
 =========================
 
-- Consider using ``build_scripts/qp5_tool.py``.
+- Consider using ``build_scripts/qfp_tool.py``.
 
 Build with address sanitizer (Linux)
 ====================================
 
-ASAN needs to be told to not exit on memory leaks and its library
-needs to be pre-loaded. Assuming the library is found
-at ``/usr/lib/gcc/x86_64-linux-gnu/11``:
+`Address sanitizer <https://clang.llvm.org/docs/AddressSanitizer.html>`_
+(ASAN) needs to be told to not exit on memory leaks and its library needs to be
+pre-loaded. Assuming the library is found at
+``/usr/lib/gcc/x86_64-linux-gnu/11``:
 
 .. code-block:: bash
 
@@ -37,6 +38,8 @@ at ``/usr/lib/gcc/x86_64-linux-gnu/11``:
     export LD_PRELOAD=/usr/lib/gcc/x86_64-linux-gnu/11/libasan.so
     python setup.py build [...] --sanitize-address
 
+Lately, this feature has been added to MVSC, too.
+
 De-Virtualize the Python Files
 ==============================
 
diff --git a/sources/pyside6/doc/developer/fix_type_hints.rst b/sources/pyside6/doc/developer/fix_type_hints.rst
new file mode 100644 (file)
index 0000000..d476a95
--- /dev/null
@@ -0,0 +1,177 @@
+Fixing Type Hints
+=================
+
+Overview
+--------
+
+If you notice any type hint issues in your project while using mypy with the PySide6 API,
+this document provides guidance on how to :ref:`identify<finding-type-hints-issues>`,
+correct, and maintain accurate type hints. Improving type hints enhances IDE support, enables
+better code completion, and improves static analysis with tools like `mypy`_.
+
+PySide6 uses `Shiboken`_ to generate Python bindings from C++ headers, but:
+
+- Some type hints are missing or too generic (e.g., ``Any``)
+- Some are incorrect due to overloads or inheritance issues
+- Some APIs are dynamic or wrapped in unusual ways
+
+Fixing these improves developer experience for all users of PySide6.
+
+.. note:: Please refer to our `contribution guideline`_.
+
+
+Tools and Setup
+---------------
+
+To find and fix the type hints ensure that your development environment has PySide6 installed.
+
+.. code-block:: bash
+
+    python -m venv venv
+    source venv/bin/activate
+    pip install PySide6 mypy
+
+or, the entire PySide6 project can be cloned:
+
+.. code-block:: bash
+
+    git clone https://code.qt.io/pyside/pyside-setup.git
+    cd pyside-setup
+
+
+Finding Type Hints Issues
+-------------------------
+
+You can locate the type hints issues using a static analysis tool, such as ``mypy``.
+
+.. code-block:: bash
+
+    mypy your_project/
+
+
+How to Fix
+----------
+
+PySide6 uses `Shiboken`_ to generate bindings, and type information can come from either static
+`typesystem`_ XML files or dynamic Python-based signature definitions.
+
+1. Fixing with Typesystem Files
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Each Qt module has an associated XML `typesystem`_ (e.g., typesystem_widgets.xml). You can specify
+or override type hints using the pyi-type attribute.
+
+Example from typesystem_gui_common.xml:
+
+.. code-block:: xml
+
+    <modify-function signature="inverted(bool*)const">
+        <modify-argument index="return" pyi-type="Tuple[PySide6.QtGui.QTransform, bool]">
+            <replace-type modified-type="PyTuple"/>
+        </modify-argument>
+    </modify-function>
+
+
+2. Fixing with Python Signature Support
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Python scripts located under `shiboken module`_.
+
+Key Files:
+
+- ``enum_sig.py``: Enumerates all signatures of a class
+- ``pyi_generator.py``: Generates ``.pyi`` files for arbitrary modules
+- ``layout.py``: Configures differently formatted versions of signatures
+- ``mapping.py``: Maps the C++ types to Python equivalents
+- ``parser.py``: Parses the signature text and creates properties for the signature objects
+
+The Python scripts here are responsible for parsing signature texts, creating signatures, and
+applying custom rules to handle special behaviors in the PySide6 API.
+
+
+Rebuild and Verify
+------------------
+
+After modifying `typesystem`_ files or Python signature logic, `rebuild`_ PySide6 to regenerate
+``.pyi`` files:
+
+.. code-block:: bash
+
+    python setup.py build --qtpaths=/path/to/qtpaths --parallel=8 --reuse-build
+
+
+This regenerates the bindings and updates the stub files used for static analysis.
+
+To verify the changes, you can either run ``mypy`` on your own code:
+
+.. code-block:: bash
+
+    mypy your_project/
+
+Or use the dedicated script provided by PySide6 to validate the generated stubs:
+
+.. code-block:: bash
+
+    python sources/pyside6/tests/pysidetest/mypy_correctness_test.py
+
+This tool runs ``mypy`` against the generated stub files to detect type annotation issues and
+inconsistencies. It is typically used to validate ``.pyi`` stubs in the build directory after
+generation.
+
+
+Special Cases and Workarounds
+-----------------------------
+
+While most type hints in PySide6 are generated automatically through `typesystem`_ XML or parsed
+function signatures, there are a number of **special cases** that require manual intervention.
+
+Missing Optional Return Values
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Some functions in the PySide6 stub files are missing ``None`` as a valid return type. This usually
+happens when the C++ method returns a pointer, which may be ``nullptr``, but the automatic
+generator assumes a non-optional return in Python. To fix these cases, PySide6 maintains a list of
+such functions in the `mapping.py`_ file.
+
+Look for the set named:
+
+.. code-block:: python
+
+    missing_optional_return = {}
+
+This is a list of functions where the return type should be wrapped in ``Optional[...]``. These
+entries override the default behavior and ensure that the generated ``.pyi`` stub files correctly
+reflect that the function may return ``None``.
+
+Char* Argument Mapping
+^^^^^^^^^^^^^^^^^^^^^^
+
+In C++, ``char*`` is commonly used to represent both binary data (as ``bytes``) and null-terminated
+strings (as ``str``). By default, PySide6 maps ``char*`` to ``bytes`` in type hints.
+
+However, some Qt functions are known to expect text input and should be treated as accepting ``str``
+instead. To handle these exceptions, PySide6 overrides the default mapping for specific functions.
+This override is defined in the `mapping.py`_ file.
+
+Look for the following logic:
+
+.. code-block:: python
+
+    # Special case - char* can either be 'bytes' or 'str'. The default is 'bytes'.
+    # Here we manually set it to map to 'str'.
+    type_map_tuple.update({("PySide6.QtCore.QObject.setProperty", "char*"): str})
+    type_map_tuple.update({("PySide6.QtCore.QObject.property", "char*"): str})
+    type_map_tuple.update({("PySide6.QtCore.QObject.inherits", "char*"): str})
+    ...
+
+Each entry is a ``(function_name, argument_type)`` tuple mapped to the correct Python type. This
+ensures that generated stubs reflect the expected usage of ``str`` rather than ``bytes``.
+
+
+.. _mypy: https://pypi.org/project/mypy/
+.. _Shiboken: https://doc.qt.io/qtforpython-6/shiboken6/index.html
+.. _typesystem: https://doc.qt.io/qtforpython-6/shiboken6/typesystem.html
+.. _contribution guideline: https://wiki.qt.io/Qt_Contribution_Guidelines
+.. _rebuild: https://doc.qt.io/qtforpython-6/building_from_source/index.html
+.. _mapping.py: https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py
+.. _shiboken module: https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature
index bddd39d91df411b097c6389519803711aa90ce0f..296b455b2f503398f3d44961ec2290008b1516fc 100644 (file)
@@ -36,3 +36,5 @@ many features and implementation details that the project has:
    signature_doc.rst
    mypy-correctness.rst
    feature-motivation.rst
+   remoteobjects.md
+   fix_type_hints.rst
index 44d3faad228251513a19e35b2aa580ad904085be..59a116277ede84306dda98ff8e8ece48e9002ef3 100644 (file)
@@ -71,33 +71,6 @@ that calls ``_PepUnicode_AsString``. The implementation was a bit involved,
 and it would be better to change the code and replace this function.
 
 
-bytesobject.h
-~~~~~~~~~~~~~
-
-The macros ``PyBytes_AS_STRING`` and ``PyBytes_GET_SIZE`` were redefined to call
-the according functions.
-
-
-floatobject.h
-~~~~~~~~~~~~~
-
-``PyFloat_AS_DOUBLE`` now calls ``PyFloat_AsDouble``.
-
-
-tupleobject.h
-~~~~~~~~~~~~~
-
-``PyTuple_GET_ITEM``, ``PyTuple_SET_ITEM`` and ``PyTuple_GET_SIZE`` were redefined as
-function calls.
-
-
-listobject.h
-~~~~~~~~~~~~
-
-``PyList_GET_ITEM``, ``PyList_SET_ITEM`` and ``PyList_GET_SIZE`` were redefined as
-function calls.
-
-
 dictobject.h
 ~~~~~~~~~~~~
 
@@ -111,13 +84,9 @@ Needed to avoid the GIL when accessing dictionaries.
 methodobject.h
 ~~~~~~~~~~~~~~
 
-``PyCFunction_GET_FUNCTION``, ``PyCFunction_GET_SELF`` and ``PyCFunction_GET_FLAGS``
-were redefined as function calls.
-
 Direct access to the methoddef structure is not available, and we defined
 ``PepCFunction_GET_NAMESTR`` as accessor for name strings.
 
-
 pythonrun.h
 ~~~~~~~~~~~
 
diff --git a/sources/pyside6/doc/developer/remoteobjects.md b/sources/pyside6/doc/developer/remoteobjects.md
new file mode 100644 (file)
index 0000000..7d4c29a
--- /dev/null
@@ -0,0 +1,162 @@
+# Qt Remote Objects Overview
+
+[Qt Remote Objects](https://doc.qt.io/qt-6/qtremoteobjects-index.html) (or QtRO)
+is described as an IPC module. That puts the focus on the internal details.
+It should be looked at more as a Connected Framework.
+
+QtRO lets you easily take an existing Qt application and interact with it from
+other devices. QtRO allows you to create a
+[_Replica_](https://doc.qt.io/qt-6/qtremoteobjects-replica.html) QObject, making
+the Replica a surrogate for the real QOject in your program (called the
+[_Source_](https://doc.qt.io/qt-6/qtremoteobjects-source.html)). You interact with
+the Replica the same way you would the Source (with one important difference) and QtRO
+ensures those interactions are forwarded to the source for handling. Changes to the
+Source are cascaded to any Replicas.
+
+The mechanism Qt Remote Objects provides for enabling these objects to connect to each
+other are a network of
+[_Nodes_](https://doc.qt.io/qt-6/qtremoteobjects-node.html). Nodes handle the details of
+connecting processes or devices. A Replica is created by calling
+[acquire()](https://doc.qt.io/qt-6/qremoteobjectnode.html#acquire) on a Node, and Sources
+are shared on the network using
+[enableRemoting()](https://doc.qt.io/qt-6/qremoteobjecthostbase.html#enableRemoting).
+
+## Replicas are _latent copies_
+
+Qt Remote Object interactions are inherently asynchronous. This _can_ lead to
+confusing results initially
+
+```python
+# Assume a replica initially has an int property `i` with a value of 2
+print(f"Value of i on replica = {replica.i}") # prints 2
+replica.iChanged.connect(lambda i: print(f"Value of i on replica changed to {i}"))
+replica.i = 3
+print(f"Value of i on replica = {replica.i}") # prints 2, not 3
+
+# When the eventloop runs, the change will be forwarded to the source instance,
+# the change will be made, and the new i value will be sent back to the replica.
+# The iChanged signal will be fired
+# after some delay.
+```
+
+Note: To avoid this confusion, Qt Remote Objects can change setters to "push"
+slots on the Replica class, making the asynchronous nature of the behavior
+clear.
+
+```python
+replica.pushI(3)  # Request a change to `i` on the source object.
+```
+
+## How does this affect PySide?
+
+PySide wraps the Qt C++ classes used by QtRO, so much of the needed
+functionality for QtRO is available in PySide. However, the interaction between
+a Source and Replica are in effect a contract that is defined on a _per object_
+basis. I.e., different objects have different APIs, and every participant must
+know about the contracts for the objects they intend to use.
+
+In C++, Qt Remote Objects leverages the
+[Replica Compiler (repc)](https://doc.qt.io/qt-6/qtremoteobjects-repc.html) to
+generate QObject header and C++ code that enforce the contracts for each type.
+REPC uses a simplified text syntax to describe the desired API in .rep files.
+REPC is integrated with qmake and cmake, simplifying the process of leveraging
+QtRO in a C++ project. The challenges in PySide are
+1) To parse the .rep file to extract the desired syntax
+2) Allow generation of types that expose the desired API and match the needed
+   contract
+3) Provide appropriate errors and handling in cases that can't be dynamically
+   handled in Python.
+For example, C++ can register templated types such as a QMap<double, MyType>
+and serialize such types once registered. While Python can create a similar
+type, there isn't a path to dynamically serialize such a type so C++ could
+interpret it correctly on the other side of a QtRO network.
+
+Under the covers, QtRO leverages Qt's QVariant infrastructure heavily. For
+instance, a Replica internally holds a QVariantList where each element
+represents one of the exposed QProperty values. The property's QVariant is
+typed appropriately for the property, allows an autogenerated getter to (for
+instance with a float property) return `return variant.value<float >();`. This
+works well with PySide converters.
+
+## RepFile PySide type
+
+The first challenge is handled by adding a Python type RepFile can takes a .rep
+file and parses it into an Abstract Syntax Tree (AST) describing the type.
+
+A simple .rep might look like:
+```cpp
+class Thermistat
+{
+    PROP(int temp)
+}
+```
+
+The desired interface would be
+```python
+from pathlib import Path
+from PySide6.QtRemoteObjects import RepFile
+
+input_file = Path(__file__).parent / "thermistat.rep"
+rep_file = RepFile(input_file)
+```
+
+The RepFile holds dictionaries `source`, `replica` and `pod`. These use the
+names of the types as the key, and the value is the PyTypeObject* of the
+generated type meeting the desired contract:
+
+```python
+Source = rep_file.source["Thermistat"]    # A Type object for Source implementation of the type
+Replica = rep_file.replica["Thermistat"]  # A Type object for Replica implementation of the type
+```
+
+## Replica type
+
+A Replica for a given interface will be a distinct type. It should be usable
+directly from Python once instantiated and initialized.
+
+```python
+Replica = rep_file.replica["Thermistat"]  # A Type object matching the Replica contract
+replica = node.acquire(Replica)           # We need to tell the node what type to instantiate
+# These two lines can be combined
+replica_instance = node.acquire(rep_file.replica["Thermistat"])
+
+# If there is a Thermistat source on the network, our replica will get connected to it.
+if replica.isInitialized():
+    print(f"The current tempeerature is {replica.temp}")
+else:
+    replica.initialized.connect(lambda: print(f"replica is now initialized. Temp = {replica.temp}"))
+```
+
+## Source type
+
+Unlike a Replica, whose interface is a passthrough of another object, the
+Source needs to actually define the desired behavior. In C++, QtRO supports two
+modes for Source objects. A MyTypeSource C++ class is autogenerated that
+defines pure virtual getters and setters. This enables full customization of
+the implementation. A MyTypeSimpleSource C++ class is also autogenerated that
+creates basic data members for properties and getters/setters that work on
+those data members.
+
+The intent is to follow the SimpleSource pattern in Python if possible.
+
+```python
+    Thermistat = rep_file.source["Thermistat"]
+    class MyThermistat(Thermistat):
+        def __init__(self, parent = None):
+            super().__init__(parent)
+            # Get the current temp from the system
+            self.temp = get_temp_from_system()
+```
+
+## Realizing Source/Replica types in python
+
+Assume there is a RepFile for thermistat.rep that defines a Thermistat class
+interface.
+
+`ThermistatReplica = repFile.replica["Thermistat"]` should be a Shiboken.ObjectType
+type, with a base of QRemoteObjectReplica's shiboken type.
+
+`ThermistatSource = repFile.source["Thermistat"]` should be a abstract class of
+Shiboken.ObjectType type, with a base of QObject's shiboken type.
+
+Both should support new classes based on their type to customize behavior.
index a93f2e9e99defc40e842a912d0eeee051424d87c..d3b7c3cf79c29cea93bec3d1194331c86e6a6943 100644 (file)
@@ -132,6 +132,22 @@ For *Qt Creator* to load and handle Python based projects, a special file is
 needed, because C++ based projects could be handle from ``.qmake`` or
 ``CMakeLists.txt`` file, which are not used with Python-based projects.
 
+The deprecated ``*.pyproject`` project files use JSON format.
+Here is an example of such file:
+
+.. code-block:: javascript
+
+    {
+        "files": ["library/server.py", "library/client.py", "logger.py"]
+    }
+
+*Qt Creator* 16.0 added support for the pyproject.toml file format, which
+is the modern standard for Python projects. PySide6 6.9.0 introduced support
+for this format, and it is recommended to use it. In order to migrate a
+.pyproject file, see `Migrating from *.pyproject to pyproject.toml`
+<https://doc.qt.io/qtforpython-6/tools/pyside6-project.html
+#migrating_from_pyproject_to_pyproject_toml>`_
+
 Old versions of *Qt Creator*, provided a simple format with the ``.pyqtc``
 extension, which were plain-text files with one-file-per-line::
 
@@ -139,14 +155,3 @@ extension, which were plain-text files with one-file-per-line::
     library/client.py
     logger.py
     ...
-
-There were limitations to this format, and further options that might be
-added that would not be supported, which was the motivation to create a
-``.pyproject`` file, which is a JSON-based file where more options could
-be added. Here is an example of such file:
-
-.. code-block:: javascript
-
-    {
-        "files": ["library/server.py", "library/client.py", "logger.py", ...]
-    }
index e7cf813e49d883a14ae7ed7b943e527a4a2020c7..c6913f3638213e9cc386d8a69c4511562d7ea94f 100644 (file)
@@ -4,23 +4,51 @@ pyside6-project
 ===============
 
 `pyside6-project` is a command line tool for creating, building and deploying
-|project| applications. It operates on a project file which is also used by
-`Qt Creator`_.
+|project| applications. It operates on project files which are also supported
+by `Qt Creator`_.
 
-Project file format
--------------------
+A project file contains a list of the source files used in the project. Typically they are
+``.py``, ``.qml``, ``.qrc``, ``.ts``, or ``.ui`` files. It can also include other subproject files.
+Generated files such as compiled resources or compiled translations should not be included in the
+project file.
 
-The project file format is a simple `JSON`_-based format with the suffix
-``.pyproject`` listing all files of the project excluding generated files
-(typically ``.py``, ``.qml``, ``.qrc``, ``.ts``, or ``.ui`` files):
+Currently, two project file formats are supported. Since PySide6 version 6.9.0, the ``*.pyproject``
+file format is deprecated in favor of the new ``pyproject.toml`` file format. The ``*.pyproject``
+file format is still supported for backward compatibility, but it is recommended to migrate to the
+new ``pyproject.toml`` file format. See
+:ref:`Migrating from *.pyproject to pyproject.toml<migrating_from_pyproject_to_pyproject_toml>`
+for more information.
+
+``pyproject.toml``
+------------------
+
+PySide6 version 6.9.0 added support for the new Python standard ``pyproject.toml`` project file
+format. It is intended to replace the deprecated ``*.pyproject`` file format. The project source
+files are listed in the ``tool.pyside6-project`` table. For example:
+
+.. code-block:: toml
+
+    [project]
+    name = "myproject"
+
+    [tool.pyside6-project]
+    files = ["main.py", "main_window.py"]
+
+More information about the ``pyproject.toml`` file format can be found in
+`Python Packaging User Guide specification: "Writing your pyproject.toml"`_.
+
+``*.pyproject``
+---------------
+
+The deprecated ``*.pyproject`` project file uses a simple `JSON`_-based format. The source files
+are listed in the ``files`` array of the JSON root object. For example:
 
 .. code-block:: json
 
     {
-        "files": ["main.py"]
+        "files": ["main.py", "main_window.py"]
     }
 
-
 Usage
 -----
 
@@ -28,8 +56,7 @@ The tool has several subcommands. New projects can be created using
 the below commands, passing the project name (directory):
 
 *new-ui*
-    Creates a new QtWidgets project with a *Qt Widgets Designer*-based main
-    window.
+    Creates a new QtWidgets project with a *Qt Widgets Designer*-based main window.
 
 *new-widget*
     Creates a new QtWidgets project with a main window.
@@ -37,11 +64,14 @@ the below commands, passing the project name (directory):
 *new-quick*
     Creates a new QtQuick project.
 
-The other commands take the project file as an argument.
+Using the optional ``--legacy-pyproject`` flag, the tool will create a legacy ``.pyproject`` file
+instead of a ``pyproject.toml`` file.
+
+The following commands can receive a project file as an optional argument.
 It is also possible to specify a directory containing the project file.
 
 *build*
-    Builds the project, generating the required build artifacts
+    Builds the project. Compiles resources, UI files, and QML files if existing and necessary.
     (see :ref:`tutorial_uifiles`, :ref:`tutorial_qrcfiles`).
 
 *run*
@@ -54,11 +84,65 @@ It is also possible to specify a directory containing the project file.
     Updates translation (.ts) files (see :ref:`tutorial_translations`).
 
 *clean*
-    Cleans the build artifacts.
+    Cleans the build artifacts. For example, compiled resources.
 
 *qmllint*
     Runs the ``qmllint`` tool, checking the QML files.
 
+*migrate-pyproject*
+    Migrates the content of one or more ``*.pyproject`` files to a ``pyproject.toml`` file.
+    See :ref:`Migrating from *.pyproject to pyproject.toml <migrating_from_pyproject_to_pyproject_toml>`.
+
+Considerations
+--------------
+
+For each file entry in the project files, ``pyside6-project`` does the following:
+
+  * ``<other project file>``: Recursively handle subproject
+  * ``<name>.qrc``: Runs the resource compiler to create a file rc_<name>.py
+  * ``<name>.ui``: Runs the user interface compiler to create a file ui_<name>.py
+
+For a Python file declaring a QML module, a directory matching the URI is
+created and populated with .qmltypes and qmldir files for use by code analysis
+tools. Currently, only one QML module consisting of several classes can be
+handled per project file.
+
+.. _migrating_from_pyproject_to_pyproject_toml:
+
+Migrating from ``*.pyproject`` to ``pyproject.toml``
+----------------------------------------------------
+
+Since PySide6 6.9.0, ``pyside6-project`` tool can create a new ``pyproject.toml`` file or update an
+existing one with the existing ``*.pyproject`` file content. To migrate an existing project, run
+``pyside6-project`` command with the ``migrate-pyproject`` argument. For example:
+
+.. code-block:: bash
+
+    pyside6-project migrate-pyproject
+
+If no file is specified, the tool will read the ``*.pyproject`` files in the current working
+directory. In the case of having multiple ``*.pyproject`` files in the same directory, its contents
+will be merged. A new ``pyproject.toml`` file will be created if not existing. If the file already
+exists, a confirmation message is displayed to the user asking for confirmation before
+updating the file with the new content. For example:
+
+.. code-block:: bash
+
+    mkdir myproject
+    cd myproject
+    echo {"files": ["main.py", "my_widget.py"]} > myproject.pyproject
+    pyside6-project migrate-pyproject
+
+Generated pyproject.toml file:
+
+.. code-block:: toml
+
+    [project]
+    name = "myproject"
+
+    [tool.pyside6-project]
+    files = ["main.py", "my_widget.py"]
 
 .. _`Qt Creator`: https://www.qt.io/product/development-tools
+.. _`Python Packaging User Guide specification: "Writing your pyproject.toml"`: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
 .. _`JSON`: https://www.json.org/
index 1b0d1c809a9ad63a31e27a15e49d3e3a73de883f..c1f56df263082765204cf08e6e3d05d04b39b5b0 100644 (file)
@@ -187,6 +187,25 @@ environment variable:
 
     export QT_LOGGING_RULES="qt.pyside.libpyside.warning=true"
 
+Thread affinity
++++++++++++++++
+
+In a multi-threaded application, signals can be emitted from senders belonging
+to a different thread than the receiver. For non-Slot type receivers, the code
+is then executed in the sender's thread context.
+
+However, for methods of :class:`~PySide6.QtCore.QObject` derived classes
+decorated with @Slot, this is usually different since they are associated with
+threads (see :meth:`~PySide6.QtCore.QObject.moveToThread`). This depends on
+last parameter of the :meth:`~PySide6.QtCore.QObject.connect` method which is
+of type :class:`PySide6.QtCore.Qt.ConnectionType`.
+
+When ``Qt.ConnectionType.AutoConnection`` (default) or
+``Qt.ConnectionType.QueuedConnection`` are passed, the receiver code will be
+executed in the thread context of the receiver object. This is useful for
+passing results from background threads into GUI classes, which need to use the
+main thread.
+
 .. _overloading-signals-and-slots:
 
 Overloading Signals and Slots with Different Types
index 8ce8f9ace1abcd05f50d625063ef5405f8248917..6ee8fa61b447e88a1830eb59233577dd3179dcf0 100644 (file)
@@ -2,7 +2,6 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtCore import Slot
 from PySide6.QtGui import QAction, QKeySequence
 from PySide6.QtWidgets import QMainWindow
 
index 40954ea3477e4b86beac28f00761785548fb1ca2..77ea4e77674a1caa44b328a63a8ea9fb209ec3f0 100644 (file)
@@ -2,11 +2,10 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtCore import QDateTime, Qt
 from PySide6.QtGui import QPainter
 from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView,
                                QSizePolicy)
-from PySide6.QtCharts import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis
+from PySide6.QtCharts import QChart, QChartView
 
 from table_model import CustomTableModel
 
@@ -41,12 +40,12 @@ class Widget(QWidget):
         self.main_layout = QHBoxLayout()
         size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
 
-        ## Left layout
+        # Left layout
         size.setHorizontalStretch(1)
         self.table_view.setSizePolicy(size)
         self.main_layout.addWidget(self.table_view)
 
-        ## Right Layout
+        # Right Layout
         size.setHorizontalStretch(4)
         self.chart_view.setSizePolicy(size)
         self.main_layout.addWidget(self.chart_view)
index 21842cff10bb4e1efff9423293303aa90e800e76..f37268df8ec6156120ee12bc76e9b661d351b8ef 100644 (file)
@@ -2,8 +2,7 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtCore import Slot
-from PySide6.QtGui import QAction, QKeySequence, QScreen
+from PySide6.QtGui import QAction, QKeySequence
 from PySide6.QtWidgets import QMainWindow
 
 
index 0817fae250bd0b8ec21896b56622cc6db5dc0c41..f074ce27d2691d757761c96a92f7c6e8cc9f3df6 100644 (file)
@@ -10,7 +10,7 @@ from PySide6.QtWidgets import (QAction, QApplication, QHeaderView, QHBoxLayout,
                                QVBoxLayout, QWidget)
 from PySide6.QtCharts import QtCharts
 
-from __feature__ import snake_case, true_property
+from __feature__ import snake_case, true_property  # noqa: F401
 
 
 class Widget(QWidget):
index 917d069bdebf65be747ab673896d7daad6d63ab8..c661aca1d02be614c03d176a6027eb5573a44ea3 100644 (file)
@@ -11,6 +11,7 @@ class MainWindow(QMainWindow):
         super().__init__()
         self.setWindowTitle("Tutorial")
 
+
 if __name__ == "__main__":
     # Qt Application
     app = QApplication(sys.argv)
index eb15e2c829ebf7419d3f1839bbb918fe4820842c..be7b4fe8bcd1f6ae851454359930fd130aa751ef 100644 (file)
@@ -131,15 +131,15 @@ the database interaction code from Python and adds the REST API interaction code
 language: python
 caption: financemodel.py
 linenos: true
-emphasize-lines: 39-61, 104-114
+emphasize-lines: 45-55, 92-103
 ---
 ```
 </details>
 
-Two methods are overridden for the `FinanceModel` class - `fetchMore` and `canFetchMore`. These
-methods are used to fetch more data from the REST API when the model is scrolled to the end. The data
-is fetched in chunks of 10 entries at a time. Additionally, the `append` method is updated to send a
-POST request to the REST API to add a new finance entry.
+A new method `fetchAllData` is added to the `FinanceModel` class to fetch all the finance data from
+the REST API. This method is called when the application starts to populate the model with the
+existing finance data. Additionally, the `append` method is updated to send a POST request to the
+REST API to add a new finance entry.
 
 ### Updating the Main Python File for the Frontend
 
index 15e9c81dca0aa95757768455903d8f4f24707e76..4b2300b1e3ea88c6e4404fda31bc2a9661dfedc0 100644 (file)
@@ -14,7 +14,7 @@ class DragDropListModel(QStringListModel):
     """A simple model that uses a QStringList as its data source."""
 
     def __init__(self, strings, parent=None):
-          super().__init__(strings, parent)
+        super().__init__(strings, parent)
 
 #! [0]
 
@@ -27,6 +27,7 @@ class DragDropListModel(QStringListModel):
 
         return True
 #! [0]
+
 #! [1]
     def dropMimeData(self, data, action, row, column, parent):
         if not self.canDropMimeData(data, action, row, column, parent):
index 6818c1aa34bf552da8a2ae028e145c6e4d70f983..46f2eb9647cc6218c1162cc47b51dd7079c0818d 100644 (file)
@@ -5,7 +5,7 @@ from __future__ import annotations
 import sys
 
 from PySide6.QtWidgets import (QApplication, QListView)
-from PySide6.QtCore import QAbstractListModel, QStringListModel, QModelIndex, Qt
+from PySide6.QtCore import QAbstractListModel, QModelIndex, Qt
 
 
 #! [0]
@@ -117,7 +117,7 @@ if __name__ == '__main__':
     model.insertRows(5, 7, QModelIndex())
     for row in range(5, 12):
         index = model.index(row, 0, QModelIndex())
-        model.setData(index, f"{row+1}")
+        model.setData(index, f"{row + 1}")
 
 #! [main5]
     view.show()
index 46b5c312a7a181c91fa0d4868d0feffdb7d759dc..b3834edb2f67b6b8cc1caccca2e45238bd2aed15 100644 (file)
@@ -2,7 +2,7 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtSql import QSqlDatabase, QSqlError, QSqlQuery
+from PySide6.QtSql import QSqlDatabase, QSqlQuery
 from datetime import date
 
 
@@ -27,6 +27,7 @@ def add_author(q, name, birthdate):
     q.exec_()
     return q.lastInsertId()
 
+
 BOOKS_SQL = """
     create table books(id integer primary key, title varchar, author integer,
                        genre integer, year integer, rating integer)
@@ -48,6 +49,7 @@ INSERT_BOOK_SQL = """
                 values(?, ?, ?, ?, ?)
     """
 
+
 def init_db():
     """
     init_db()
@@ -74,12 +76,12 @@ def init_db():
     greeneId = add_author(q, "Graham Greene", date(1904, 10, 2))
     pratchettId = add_author(q, "Terry Pratchett", date(1948, 4, 28))
 
-    check(q.prepare,INSERT_GENRE_SQL)
+    check(q.prepare, INSERT_GENRE_SQL)
     sfiction = add_genre(q, "Science Fiction")
     fiction = add_genre(q, "Fiction")
     fantasy = add_genre(q, "Fantasy")
 
-    check(q.prepare,INSERT_BOOK_SQL)
+    check(q.prepare, INSERT_BOOK_SQL)
     add_book(q, "Foundation", 1951, asimovId, sfiction, 3)
     add_book(q, "Foundation and Empire", 1952, asimovId, sfiction, 4)
     add_book(q, "Second Foundation", 1953, asimovId, sfiction, 3)
index 5e926bacaf10f39e3aca0f9182d5c8b7592c131f..d83312e91f2534a28517aef5f707d5b3036bd66e 100644 (file)
@@ -3,14 +3,12 @@
 from __future__ import annotations
 
 import copy
-import os
 from pathlib import Path
 
 from PySide6.QtSql import QSqlRelationalDelegate
-from PySide6.QtWidgets import (QItemDelegate, QSpinBox, QStyledItemDelegate,
-    QStyle, QStyleOptionViewItem)
-from PySide6.QtGui import QMouseEvent, QPixmap, QPalette, QImage
-from PySide6.QtCore import QEvent, QSize, Qt, QUrl
+from PySide6.QtWidgets import QSpinBox, QStyle
+from PySide6.QtGui import QPixmap, QPalette
+from PySide6.QtCore import QEvent, QSize, Qt
 
 
 class BookDelegate(QSqlRelationalDelegate):
@@ -49,7 +47,7 @@ class BookDelegate(QSqlRelationalDelegate):
 
             if option.state & QStyle.State_Selected:
                 painter.fillRect(option.rect,
-                    option.palette.color(color_group, QPalette.Highlight))
+                                 option.palette.color(color_group, QPalette.Highlight))
             rating = model.data(index, Qt.DisplayRole)
             width = self.star.width()
             height = self.star.height()
index 46b5c312a7a181c91fa0d4868d0feffdb7d759dc..b3834edb2f67b6b8cc1caccca2e45238bd2aed15 100644 (file)
@@ -2,7 +2,7 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtSql import QSqlDatabase, QSqlError, QSqlQuery
+from PySide6.QtSql import QSqlDatabase, QSqlQuery
 from datetime import date
 
 
@@ -27,6 +27,7 @@ def add_author(q, name, birthdate):
     q.exec_()
     return q.lastInsertId()
 
+
 BOOKS_SQL = """
     create table books(id integer primary key, title varchar, author integer,
                        genre integer, year integer, rating integer)
@@ -48,6 +49,7 @@ INSERT_BOOK_SQL = """
                 values(?, ?, ?, ?, ?)
     """
 
+
 def init_db():
     """
     init_db()
@@ -74,12 +76,12 @@ def init_db():
     greeneId = add_author(q, "Graham Greene", date(1904, 10, 2))
     pratchettId = add_author(q, "Terry Pratchett", date(1948, 4, 28))
 
-    check(q.prepare,INSERT_GENRE_SQL)
+    check(q.prepare, INSERT_GENRE_SQL)
     sfiction = add_genre(q, "Science Fiction")
     fiction = add_genre(q, "Fiction")
     fantasy = add_genre(q, "Fantasy")
 
-    check(q.prepare,INSERT_BOOK_SQL)
+    check(q.prepare, INSERT_BOOK_SQL)
     add_book(q, "Foundation", 1951, asimovId, sfiction, 3)
     add_book(q, "Foundation and Empire", 1952, asimovId, sfiction, 4)
     add_book(q, "Second Foundation", 1953, asimovId, sfiction, 3)
index 735d8e021016741217af221a62e9707fe282bea0..e125314de9001551297b1957ab3fe0c711c22bad 100644 (file)
@@ -4,7 +4,6 @@ from __future__ import annotations
 
 import sys
 
-from PySide6.QtCore import Qt
 from PySide6.QtSql import QSqlQueryModel
 from PySide6.QtWidgets import QTableView, QApplication
 
index cfe52bba844042e581b7eb9fc901011d3087b7ed..ecd0afd1f64d13a7c8a0d54bf7dad464ed89d9d8 100644 (file)
@@ -3,14 +3,12 @@
 from __future__ import annotations
 
 import copy
-import os
 from pathlib import Path
 
 from PySide6.QtSql import QSqlRelationalDelegate
-from PySide6.QtWidgets import (QItemDelegate, QSpinBox, QStyledItemDelegate,
-    QStyle, QStyleOptionViewItem)
-from PySide6.QtGui import QMouseEvent, QPixmap, QPalette, QImage
-from PySide6.QtCore import QEvent, QSize, Qt, QUrl
+from PySide6.QtWidgets import QSpinBox, QStyle
+from PySide6.QtGui import QPixmap, QPalette
+from PySide6.QtCore import QEvent, QSize, Qt
 
 
 class BookDelegate(QSqlRelationalDelegate):
@@ -49,7 +47,7 @@ class BookDelegate(QSqlRelationalDelegate):
 
             if option.state & QStyle.State_Selected:
                 painter.fillRect(option.rect,
-                    option.palette.color(color_group, QPalette.Highlight))
+                                 option.palette.color(color_group, QPalette.Highlight))
             rating = model.data(index, Qt.DisplayRole)
             width = self.star.width()
             height = self.star.height()
index 6aba9dd938f76a68b046ca320a4a43ae3f8cf132..6d96d6a803211851e45702e33e4cfc374e9d0392 100644 (file)
@@ -2,18 +2,17 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-import copy, os
 from PySide6.QtSql import QSqlRelationalDelegate
-from PySide6.QtWidgets import (QItemDelegate, QSpinBox, QStyledItemDelegate,
-    QStyle, QStyleOptionViewItem)
-from PySide6.QtGui import QMouseEvent, QPixmap, QPalette, QImage
-from PySide6.QtCore import QEvent, QSize, Qt, QUrl
+from PySide6.QtWidgets import QSpinBox, QStyle
+from PySide6.QtGui import QPixmap, QPalette
+from PySide6.QtCore import QEvent, QSize, Qt
+
 
 class BookDelegate(QSqlRelationalDelegate):
     """Books delegate to rate the books"""
 
     def __init__(self, star_png, parent=None):
-        QSqlRelationalDelegate.__init__(self, parent)
+        super().__init__(parent)
         self.star = QPixmap(":/images/star.png")
 
     def paint(self, painter, option, index):
@@ -29,9 +28,7 @@ class BookDelegate(QSqlRelationalDelegate):
         """
         if index.column() != 5:
             # Since we draw the grid ourselves:
-            opt = copy.copy(option)
-            opt.rect = option.rect.adjusted(0, 0, -1, -1)
-            QSqlRelationalDelegate.paint(self, painter, opt, index)
+            QSqlRelationalDelegate.paint(self, painter, option, index)
         else:
             model = index.model()
             if option.state & QStyle.State_Enabled:
@@ -44,7 +41,7 @@ class BookDelegate(QSqlRelationalDelegate):
 
             if option.state & QStyle.State_Selected:
                 painter.fillRect(option.rect,
-                    option.palette.color(color_group, QPalette.Highlight))
+                                 option.palette.color(color_group, QPalette.Highlight))
             rating = model.data(index, Qt.DisplayRole)
             width = self.star.width()
             height = self.star.height()
@@ -54,9 +51,6 @@ class BookDelegate(QSqlRelationalDelegate):
                 painter.drawPixmap(x, y, self.star)
                 x += width
 
-            # Since we draw the grid ourselves:
-            self.drawFocus(painter, option, option.rect.adjusted(0, 0, -1, -1))
-
         pen = painter.pen()
         painter.setPen(option.palette.color(QPalette.Mid))
         painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
index e115c321dae8bd6942fe3293504123bf1ff8e900..907af92c5af38ca48ca9c60e2cea09c933b246af 100644 (file)
@@ -2,13 +2,11 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtGui import QAction
 from PySide6.QtWidgets import (QAbstractItemView, QDataWidgetMapper,
-    QHeaderView, QMainWindow, QMessageBox)
+                               QHeaderView, QMainWindow, QMessageBox)
 from PySide6.QtGui import QKeySequence
-from PySide6.QtSql import (QSqlRelation, QSqlRelationalTableModel, QSqlTableModel,
-    QSqlError)
-from PySide6.QtCore import QAbstractItemModel, QObject, QSize, Qt, Slot
+from PySide6.QtSql import QSqlRelation, QSqlRelationalTableModel, QSqlTableModel
+from PySide6.QtCore import Qt
 import createdb
 from ui_bookwindow import Ui_BookWindow
 from bookdelegate import BookDelegate
@@ -60,8 +58,8 @@ class BookWindow(QMainWindow, Ui_BookWindow):
         self.genreEdit.setModelColumn(model.relationModel(genre_idx).fieldIndex("name"))
 
         # Lock and prohibit resizing of the width of the rating column:
-        self.bookTable.horizontalHeader().setSectionResizeMode(model.fieldIndex("rating"),
-            QHeaderView.ResizeToContents)
+        header = self.bookTable.horizontalHeader()
+        header.setSectionResizeMode(model.fieldIndex("rating"), QHeaderView.ResizeToContents)
 
         mapper = QDataWidgetMapper(self)
         mapper.setModel(model)
@@ -78,23 +76,23 @@ class BookWindow(QMainWindow, Ui_BookWindow):
         self.bookTable.setCurrentIndex(model.index(0, 0))
         self.create_menubar()
 
-    def showError(err):
+    def showError(self, err):
         QMessageBox.critical(self, "Unable to initialize Database",
-                    "Error initializing database: " + err.text())
+                             "Error initializing database: " + err.text())
 
     def create_menubar(self):
         file_menu = self.menuBar().addMenu(self.tr("&File"))
         quit_action = file_menu.addAction(self.tr("&Quit"))
-        quit_action.triggered.connect(qApp.quit)
+        quit_action.triggered.connect(qApp.quit)  # noqa: F821
 
         help_menu = self.menuBar().addMenu(self.tr("&Help"))
         about_action = help_menu.addAction(self.tr("&About"))
         about_action.setShortcut(QKeySequence.HelpContents)
         about_action.triggered.connect(self.about)
         aboutQt_action = help_menu.addAction("&About Qt")
-        aboutQt_action.triggered.connect(qApp.aboutQt)
+        aboutQt_action.triggered.connect(qApp.aboutQt)  # noqa: F821
 
     def about(self):
-        QMessageBox.about(self, self.tr("About Books"),
-            self.tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
-                "with a model/view framework."))
+        message = self.tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
+                          "with a model/view framework.")
+        QMessageBox.about(self, self.tr("About Books"), message)
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/chapter3.pyproject b/sources/pyside6/doc/tutorials/portingguide/chapter3/chapter3.pyproject
new file mode 100644 (file)
index 0000000..3f755a8
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["main.py", "bookdelegate.py", "books.qrc", "bookwindow.py", "bookwindow.ui", "createdb.py"]
+}
index 46b5c312a7a181c91fa0d4868d0feffdb7d759dc..b3834edb2f67b6b8cc1caccca2e45238bd2aed15 100644 (file)
@@ -2,7 +2,7 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtSql import QSqlDatabase, QSqlError, QSqlQuery
+from PySide6.QtSql import QSqlDatabase, QSqlQuery
 from datetime import date
 
 
@@ -27,6 +27,7 @@ def add_author(q, name, birthdate):
     q.exec_()
     return q.lastInsertId()
 
+
 BOOKS_SQL = """
     create table books(id integer primary key, title varchar, author integer,
                        genre integer, year integer, rating integer)
@@ -48,6 +49,7 @@ INSERT_BOOK_SQL = """
                 values(?, ?, ?, ?, ?)
     """
 
+
 def init_db():
     """
     init_db()
@@ -74,12 +76,12 @@ def init_db():
     greeneId = add_author(q, "Graham Greene", date(1904, 10, 2))
     pratchettId = add_author(q, "Terry Pratchett", date(1948, 4, 28))
 
-    check(q.prepare,INSERT_GENRE_SQL)
+    check(q.prepare, INSERT_GENRE_SQL)
     sfiction = add_genre(q, "Science Fiction")
     fiction = add_genre(q, "Fiction")
     fantasy = add_genre(q, "Fantasy")
 
-    check(q.prepare,INSERT_BOOK_SQL)
+    check(q.prepare, INSERT_BOOK_SQL)
     add_book(q, "Foundation", 1951, asimovId, sfiction, 3)
     add_book(q, "Foundation and Empire", 1952, asimovId, sfiction, 4)
     add_book(q, "Second Foundation", 1953, asimovId, sfiction, 3)
index 9af868772538bec5b974f157010777225ad55c0d..4b4aee1a319048e795c8ccf8ff55d06eceb3187a 100644 (file)
@@ -5,7 +5,7 @@ from __future__ import annotations
 import sys
 from PySide6.QtWidgets import QApplication
 from bookwindow import BookWindow
-import rc_books
+import rc_books  # noqa: F401
 
 if __name__ == "__main__":
     app = QApplication([])
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/rc_books.py b/sources/pyside6/doc/tutorials/portingguide/chapter3/rc_books.py
new file mode 100644 (file)
index 0000000..009d355
--- /dev/null
@@ -0,0 +1,88 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.9.0
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x03\x0e\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xffa\
+\x00\x00\x00\x09pHYs\x00\x00\x0b\x11\x00\x00\x0b\x11\
+\x01\x7fd_\x91\x00\x00\x00\x07tIME\x07\xd4\x09\
+\x03\x12\x11\x08\x18~\xe5:\x00\x00\x00\x06bKGD\
+\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x02\x9bID\
+AT8\xcbc\x98:c\x1e#:\xe6\xe5d\xcf\x17\
+\x12\x12\x16\xc4&\x87\x8e\x19\xb0\x09v\xc6\x18\xb7x\xea\
+\x8b\xcd\x9c=o\x09i\x06,X4\x8f\xf1\xd2\xa5\x99\
+L\xb9\xa1\x16\xc5\xc7\xbb\xed\xff\x0a\xf2\xb2;M\x9f\xb5\
+\x908\x03\x16,\x9a\xcb\xf8\xe0\xde\x04\x96\xc7\x0f\xdby\
+\xe7MO\xc8\xfbv\xbf\xe5\xff\xb4\x0a\x9b\x9by\x851\
+\xdc\xd3g-\x82k\x983\x7f)\xe3l F1`\
+\xca\xf4y\x8c\xd7\xaeMg\x02i~\xf2\xa8Y\xe1\xd2\
+\xa5\xfa\xdc_\x9f7\xfd\xffx\xbf\xea\x7fE\x96m\x97\
+\x81\x81>'33\x8b\xa5\x9e8gi\xb8\x9e\xc0f\
+&&\xa6D\x14\x03&N\x9d\xc7x\xef\xdeD\x96'\
+\x0f[E\x9f>j\xd6\xbdu\xb3\x22\xef\xd7\xb7=\xff\
+\xbe\x7f\xe8\xfb\x7f~S\xcc\xef\x05\xc5\xea\x9fNOQ\
+\xfb\x7f\xbaM\xed\xbf\x87\x1a\xefn5-\x1dV\x14\x03\
+f\xcf[\xce\xa8\xa4\xa9![W\xed\x9b}\xefJ\xcb\
+\xcew\xaf&\x7f\xfa\xfee\xc9\xff\xef\x1f\xfa\xff\xbf\xbf\
+\x95\xf2\xff\xc9^\x83\xffW\x17\xaa\xfdot\x12{\xc4\
+\xc7\xc7/\x8e\x12\x06Y\xb9\x85\xcc\xb2\x82\x1c\xf3\xa7D\
+\xab\xfe\xfa\xbe%\xe2\xff\x8fgm\xff\x7f|\x9a\x08\xd6\
+\xfc\xf5Y\xcd\xff\xd7\xe7\xfc\xfe\xdf\xde\xa0\xf5\x7fE\x94\
+\xecO\x16\x16V\xebi3\xe7\xa3\x06\xe2\xe4is\x18\
+\xe7/Z\xc1\xc8\xce\xc1i\x10\xe5\xa8\xd2\xbe\xa6\xcd\xe7\
+\xf6\xc3m\x99\xff~^(\xf8\xff\xe1j\xe0\xff\x17G\
+L\xff\xdf\xdf\xae\xf6\xbf\xc2]\xf4\xba\x9a\x9a\x06\x1bF\
+,\x00\x01#2\xe6\xe6\xe6`Q\xd6\x941_\xde\xe4\
+q\xfb\xc3y\xd3\xff\x1b\x8aT\xff\xbf?`\xff\xff\xdc\
+l\xe5\xff\xea\xc2\x1c9\xd3g-\xc0i\x00\x13\x10\x8b\
+\x03\xb1?\x10\xe7\xf5\x16\xd8\xde\xf8p\xc6\xe4\xbf\x9d<\
+\xf7t\x7fC\xe9\x95\xb7\x96\xd9\xff\x9b\x9c,\xfdN@\
+@H\x14\x9b\x01LP,\x06\xc4\x19@|\x22;\xca\
+\xf0\xe7\xe9\xf9\x06\xff\x81\xec\x03@\xbc^\x82\x9f\xf3\xf6\
+\x9e\x1a\xf3_az\x823P\xd2\x01T#3\x10\x0b\
+\x00\xb1\x1e\x10\x17\x03\xf1\xd1\xa8@\xdd\x9f\xad\x09J \
+\x03\xfe\x00\xf17 >\x0f\xb4kf\xb9\xa7\xea\x0d}\
+i>#d\x03\xb4\x808\x08\x88k\x81x\x09\xd4\xc6\
+\x1b\x11a\x06\xdf\xec\x94\xb8\xdf\x03\xd9;\x81x\x1a\x10\
+\xf7\x82\xd4\xb0\xb2\xb1G\xf9\xda\x99:L\x9d9\x9f\x09\
+f\x80\x0e\x10;\x02\xb1\x13\x10[\x00\xb1\x01\x10\x07\x06\
+{h\x9c\x02\xd2k\xa0\x86\x8b\x001\x17\x10\xf3\x80\xb0\
+\x88\x88(\xcb,hFC\xf6\x02\x08\xb3\x001+\x10\
+K122\xe4\x01i7 \x96\x01b6\xa88\x0b\
+T=cW\xef$\xb0\x01\x00\xceo{\xf5UL\xf0\
+\xac\x00\x00\x00\x00IEND\xaeB`\x82\
+"
+
+qt_resource_name = b"\
+\x00\x06\
+\x07\x03}\xc3\
+\x00i\
+\x00m\x00a\x00g\x00e\x00s\
+\x00\x08\
+\x0a\x85X\x07\
+\x00s\
+\x00t\x00a\x00r\x00.\x00p\x00n\x00g\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01}\xc3\x96\xdb\xbd\
+"
+
+def qInitResources():
+    QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+def qCleanupResources():
+    QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+qInitResources()
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/ui_bookwindow.py b/sources/pyside6/doc/tutorials/portingguide/chapter3/ui_bookwindow.py
new file mode 100644 (file)
index 0000000..542b661
--- /dev/null
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'bookwindow.ui'
+##
+## Created by: Qt User Interface Compiler version 6.9.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+    QMetaObject, QObject, QPoint, QRect,
+    QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+    QFont, QFontDatabase, QGradient, QIcon,
+    QImage, QKeySequence, QLinearGradient, QPainter,
+    QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractItemView, QApplication, QComboBox, QFormLayout,
+    QGroupBox, QHeaderView, QLabel, QLineEdit,
+    QMainWindow, QSizePolicy, QSpinBox, QTableView,
+    QVBoxLayout, QWidget)
+
+class Ui_BookWindow(object):
+    def setupUi(self, BookWindow):
+        if not BookWindow.objectName():
+            BookWindow.setObjectName(u"BookWindow")
+        BookWindow.resize(601, 420)
+        self.centralWidget = QWidget(BookWindow)
+        self.centralWidget.setObjectName(u"centralWidget")
+        self.vboxLayout = QVBoxLayout(self.centralWidget)
+#ifndef Q_OS_MAC
+        self.vboxLayout.setSpacing(6)
+#endif
+#ifndef Q_OS_MAC
+        self.vboxLayout.setContentsMargins(9, 9, 9, 9)
+#endif
+        self.vboxLayout.setObjectName(u"vboxLayout")
+        self.groupBox = QGroupBox(self.centralWidget)
+        self.groupBox.setObjectName(u"groupBox")
+        self.vboxLayout1 = QVBoxLayout(self.groupBox)
+#ifndef Q_OS_MAC
+        self.vboxLayout1.setSpacing(6)
+#endif
+#ifndef Q_OS_MAC
+        self.vboxLayout1.setContentsMargins(9, 9, 9, 9)
+#endif
+        self.vboxLayout1.setObjectName(u"vboxLayout1")
+        self.bookTable = QTableView(self.groupBox)
+        self.bookTable.setObjectName(u"bookTable")
+        self.bookTable.setSelectionBehavior(QAbstractItemView.SelectRows)
+
+        self.vboxLayout1.addWidget(self.bookTable)
+
+        self.groupBox_2 = QGroupBox(self.groupBox)
+        self.groupBox_2.setObjectName(u"groupBox_2")
+        self.formLayout = QFormLayout(self.groupBox_2)
+        self.formLayout.setObjectName(u"formLayout")
+        self.label_5 = QLabel(self.groupBox_2)
+        self.label_5.setObjectName(u"label_5")
+
+        self.formLayout.setWidget(0, QFormLayout.ItemRole.LabelRole, self.label_5)
+
+        self.titleEdit = QLineEdit(self.groupBox_2)
+        self.titleEdit.setObjectName(u"titleEdit")
+        self.titleEdit.setEnabled(True)
+
+        self.formLayout.setWidget(0, QFormLayout.ItemRole.FieldRole, self.titleEdit)
+
+        self.label_2_2_2_2 = QLabel(self.groupBox_2)
+        self.label_2_2_2_2.setObjectName(u"label_2_2_2_2")
+
+        self.formLayout.setWidget(1, QFormLayout.ItemRole.LabelRole, self.label_2_2_2_2)
+
+        self.authorEdit = QComboBox(self.groupBox_2)
+        self.authorEdit.setObjectName(u"authorEdit")
+        self.authorEdit.setEnabled(True)
+
+        self.formLayout.setWidget(1, QFormLayout.ItemRole.FieldRole, self.authorEdit)
+
+        self.label_3 = QLabel(self.groupBox_2)
+        self.label_3.setObjectName(u"label_3")
+
+        self.formLayout.setWidget(2, QFormLayout.ItemRole.LabelRole, self.label_3)
+
+        self.genreEdit = QComboBox(self.groupBox_2)
+        self.genreEdit.setObjectName(u"genreEdit")
+        self.genreEdit.setEnabled(True)
+
+        self.formLayout.setWidget(2, QFormLayout.ItemRole.FieldRole, self.genreEdit)
+
+        self.label_4 = QLabel(self.groupBox_2)
+        self.label_4.setObjectName(u"label_4")
+
+        self.formLayout.setWidget(3, QFormLayout.ItemRole.LabelRole, self.label_4)
+
+        self.yearEdit = QSpinBox(self.groupBox_2)
+        self.yearEdit.setObjectName(u"yearEdit")
+        self.yearEdit.setEnabled(True)
+        self.yearEdit.setMaximum(2100)
+        self.yearEdit.setMinimum(-1000)
+
+        self.formLayout.setWidget(3, QFormLayout.ItemRole.FieldRole, self.yearEdit)
+
+        self.label = QLabel(self.groupBox_2)
+        self.label.setObjectName(u"label")
+
+        self.formLayout.setWidget(4, QFormLayout.ItemRole.LabelRole, self.label)
+
+        self.ratingEdit = QSpinBox(self.groupBox_2)
+        self.ratingEdit.setObjectName(u"ratingEdit")
+        self.ratingEdit.setMaximum(5)
+
+        self.formLayout.setWidget(4, QFormLayout.ItemRole.FieldRole, self.ratingEdit)
+
+
+        self.vboxLayout1.addWidget(self.groupBox_2)
+
+
+        self.vboxLayout.addWidget(self.groupBox)
+
+        BookWindow.setCentralWidget(self.centralWidget)
+        QWidget.setTabOrder(self.bookTable, self.titleEdit)
+        QWidget.setTabOrder(self.titleEdit, self.authorEdit)
+        QWidget.setTabOrder(self.authorEdit, self.genreEdit)
+        QWidget.setTabOrder(self.genreEdit, self.yearEdit)
+
+        self.retranslateUi(BookWindow)
+
+        QMetaObject.connectSlotsByName(BookWindow)
+    # setupUi
+
+    def retranslateUi(self, BookWindow):
+        BookWindow.setWindowTitle(QCoreApplication.translate("BookWindow", u"Books", None))
+        self.groupBox.setTitle(QCoreApplication.translate("BookWindow", u"Books", None))
+        self.groupBox_2.setTitle(QCoreApplication.translate("BookWindow", u"Details", None))
+        self.label_5.setText(QCoreApplication.translate("BookWindow", u"<b>Title:</b>", None))
+        self.label_2_2_2_2.setText(QCoreApplication.translate("BookWindow", u"<b>Author: </b>", None))
+        self.label_3.setText(QCoreApplication.translate("BookWindow", u"<b>Genre:</b>", None))
+        self.label_4.setText(QCoreApplication.translate("BookWindow", u"<b>Year:</b>", None))
+        self.yearEdit.setPrefix("")
+        self.label.setText(QCoreApplication.translate("BookWindow", u"<b>Rating:</b>", None))
+    # retranslateUi
index 3b234bdf8df740990f3de3180becb9d8d7875677..c4df300605accd2cf84ffb1d502309d0c9f4b5e2 100644 (file)
@@ -5,10 +5,10 @@ from __future__ import annotations
 import sys
 import random
 
-from PySide6.QtWidgets import (QApplication, QLabel,
-     QPushButton, QVBoxLayout, QWidget)
+from PySide6.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidget
 from PySide6.QtCore import Qt, Slot
 
+
 class MyWidget(QWidget):
     def __init__(self):
         super().__init__()
@@ -30,6 +30,7 @@ class MyWidget(QWidget):
     def magic(self):
         self.text.setText(random.choice(self.hello))
 
+
 if __name__ == "__main__":
     app = QApplication(sys.argv)
 
index 1ad649a4598cfe6b3f38acea088f3f3677fabadc..539f1f3299667660429bbf854fef4a344fa12bbb 100644 (file)
@@ -3,6 +3,8 @@
 
 project(libpyside)
 
+find_package(Qt6 COMPONENTS Core CorePrivate)
+
 set(libpyside_libraries Qt::Core Qt::CorePrivate)
 
 set(CMAKE_AUTOMOC ON)
index 9be7dccb0a2b1d0aea6a84ba41b6db03bcf62cf3..89320977a915cd3eb3dbbe4c73dc1cf2daaae65b 100644 (file)
@@ -2,12 +2,13 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "class_property.h"
-#include "pysidestaticstrings.h"
 #include "feature_select.h"
 
+#include <basewrapper.h>
 #include <pep384ext.h>
-#include <shiboken.h>
 #include <sbkstaticstrings.h>
+#include <sbktypefactory.h>
+#include <signature.h>
 
 extern "C" {
 
@@ -153,13 +154,13 @@ namespace PySide::ClassProperty {
 
 static const char *PyClassProperty_SignatureStrings[] = {
     "PySide6.QtCore.PyClassProperty(cls,"
-        "fget:typing.Optional[typing.Callable[[typing.Any],typing.Any]]=None,"
-        "fset:typing.Optional[typing.Callable[[typing.Any,typing.Any],None]]=None,"
-        "fdel:typing.Optional[typing.Callable[[typing.Any],None]]=None,"
+        "fget:typing.Optional[collections.abc.Callable[[typing.Any],typing.Any]]=None,"
+        "fset:typing.Optional[collections.abc.Callable[[typing.Any,typing.Any],None]]=None,"
+        "fdel:typing.Optional[collections.abc.Callable[[typing.Any],None]]=None,"
         "doc:typing.Optional[str]=None)",
-    "PySide6.QtCore.PyClassProperty.getter(cls,fget:typing.Callable[[typing.Any],typing.Any])->PySide6.QtCore.PyClassProperty",
-    "PySide6.QtCore.PyClassProperty.setter(cls,fset:typing.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.PyClassProperty",
-    "PySide6.QtCore.PyClassProperty.deleter(cls,fdel:typing.Callable[[typing.Any],None])->PySide6.QtCore.PyClassProperty",
+    "PySide6.QtCore.PyClassProperty.getter(cls,fget:collections.abc.Callable[[typing.Any],typing.Any])->PySide6.QtCore.PyClassProperty",
+    "PySide6.QtCore.PyClassProperty.setter(cls,fset:collections.abc.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.PyClassProperty",
+    "PySide6.QtCore.PyClassProperty.deleter(cls,fdel:collections.abc.Callable[[typing.Any],None])->PySide6.QtCore.PyClassProperty",
     nullptr}; // Sentinel
 
 void init(PyObject *module)
index 95acab869f1d7da402af2693a99d620ac7d13807..a3b718d834fa78c986c68574497dfa4c309adb72 100644 (file)
@@ -2,7 +2,6 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "dynamicqmetaobject.h"
-#include "pysidelogging_p.h"
 #include "pysideqobject.h"
 #include "pysidesignal.h"
 #include "pysidesignal_p.h"
 #include "pysideproperty_p.h"
 #include "pysideslot_p.h"
 #include "pysideqenum.h"
-#include "pyside_p.h"
 #include "pysidestaticstrings.h"
 
-#include <shiboken.h>
+#include <autodecref.h>
+#include <gilstate.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
 
-#include <QtCore/QByteArray>
-#include <QtCore/QObject>
-#include <QtCore/QStringList>
-#include <QtCore/QTextStream>
-#include <QtCore/QList>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qlist.h>
 #include <private/qmetaobjectbuilder_p.h>
 
 #include <cstring>
@@ -591,7 +592,7 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type)
     basesToCheck.push_back(type);
 
     auto *sbkObjTypeF = SbkObject_TypeF();
-    auto *baseObjType = reinterpret_cast<PyTypeObject *>(&PyBaseObject_Type);
+    auto *baseObjType = &PyBaseObject_Type;
     for (Py_ssize_t i = 0; i < basesCount; ++i) {
         auto *baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(mro, i));
         if (baseType != sbkObjTypeF && baseType != baseObjType
index 4ea5351c444ff8e1a1dd856a820aa77f3b63b6eb..fd5a5f55280379055ff91fc75f4cf3009cae801b 100644 (file)
@@ -7,8 +7,8 @@
 #include <sbkpython.h>
 #include <pysidemacros.h>
 
-#include <QtCore/QMetaObject>
-#include <QtCore/QMetaMethod>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qmetaobject.h>
 
 #include <utility>
 
index 1fbdba1ab9ebba203199b9f45585ea8ecbedf1df..1af286424c99bec5772e68730b9e9f45daee00f1 100644 (file)
@@ -8,14 +8,15 @@
 #include "signalmanager.h"
 
 #include <autodecref.h>
+#include <helper.h>
 #include <gilstate.h>
 #include <pep384ext.h>
 
-#include <QtCore/QDebug>
-#include <QtCore/QtCompare>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QHash>
-#include <QtCore/QPointer>
+#include <QtCore/qdebug.h>
+#include <QtCore/qcompare.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qpointer.h>
 
 namespace PySide
 {
@@ -26,7 +27,7 @@ DynamicSlot::SlotType DynamicSlot::slotType(PyObject *callback)
 {
     if (PyMethod_Check(callback) != 0)
         return SlotType::Method;
-    if (PySide::isCompiledMethod(callback) != 0)
+    if (Shiboken::isCompiledMethod(callback))
         return SlotType::CompiledMethod;
     if (PyCFunction_Check(callback) != 0)
         return SlotType::C_Function;
@@ -132,41 +133,8 @@ void MethodDynamicSlot::formatDebug(QDebug &debug) const
         << ", function=" << PySide::debugPyObject(m_function) << ')';
 }
 
-// Store a weak reference on pythonSelf.
-class TrackingMethodDynamicSlot : public MethodDynamicSlot
-{
-    Q_DISABLE_COPY_MOVE(TrackingMethodDynamicSlot)
-public:
-    explicit TrackingMethodDynamicSlot(PyObject *function, PyObject *pythonSelf,
-                                       PyObject *weakRef);
-    ~TrackingMethodDynamicSlot() override;
-
-    void releaseWeakRef() { m_weakRef = nullptr; }
-
-private:
-    PyObject *m_weakRef;
-};
-
-TrackingMethodDynamicSlot::TrackingMethodDynamicSlot(PyObject *function, PyObject *pythonSelf,
-                                                     PyObject *weakRef) :
-    MethodDynamicSlot(function, pythonSelf),
-    m_weakRef(weakRef)
-{
-}
-
-TrackingMethodDynamicSlot::~TrackingMethodDynamicSlot()
-{
-    if (m_weakRef != nullptr) {
-        Shiboken::GilState gil;
-        // weakrefs must not be de-refed after the object has been deleted,
-        // else they get negative refcounts.
-        if (PepExt_Weakref_IsAlive(m_weakRef))
-            Py_DECREF(m_weakRef);
-    }
-}
-
 // Delete the connection on receiver deletion by weakref
-class PysideReceiverMethodSlot : public TrackingMethodDynamicSlot
+class PysideReceiverMethodSlot : public MethodDynamicSlot
 {
     Q_DISABLE_COPY_MOVE(PysideReceiverMethodSlot)
 public:
@@ -177,19 +145,21 @@ public:
 
 static void onPysideReceiverSlotDestroyed(void *data)
 {
-    auto *self = reinterpret_cast<PysideReceiverMethodSlot *>(data);
-    // Ensure the weakref is gone in case the connection stored in
-    // Qt's internals outlives Python.
-    self->releaseWeakRef();
+    auto *pythonSelf = reinterpret_cast<PyObject *>(data);
     Py_BEGIN_ALLOW_THREADS
-    disconnectReceiver(self->pythonSelf());
+    disconnectReceiver(pythonSelf);
     Py_END_ALLOW_THREADS
 }
 
 PysideReceiverMethodSlot::PysideReceiverMethodSlot(PyObject *function, PyObject *pythonSelf) :
-    TrackingMethodDynamicSlot(function, pythonSelf,
-                              WeakRef::create(pythonSelf, onPysideReceiverSlotDestroyed, this))
+    MethodDynamicSlot(function, pythonSelf)
 {
+    // PYSIDE-3148: The weakref is automatically deleted when the notification triggers.
+    // Note that notifications may trigger after deletion of TrackingMethodDynamicSlot in case
+    // of multiple connections to the same receiver, so, &DynamicSlot must not be used as user
+    // data. Also trying to actively deref a pending weak ref from ~TrackingMethodDynamicSlot()
+    // does not reliably prevent the notification from being triggered.
+    WeakRef::create(pythonSelf, onPysideReceiverSlotDestroyed, pythonSelf);
 }
 
 DynamicSlot* DynamicSlot::create(PyObject *callback)
@@ -348,6 +318,7 @@ public Q_SLOTS:
 
 void SenderSignalDeletionTracker::senderDestroyed(QObject *o)
 {
+    Shiboken::GilState gil; // PYSIDE-3072
     for (auto it = connectionHash.begin(); it != connectionHash.end(); ) {
         if (it.key().sender == o)
             it = connectionHash.erase(it);
index bad05d7f4664ed1676181343f5c9ed5b58755d8e..6e8b938edc38a63be1c8a17bc4e44c5a7b714fff 100644 (file)
@@ -6,8 +6,8 @@
 
 #include <sbkpython.h>
 
-#include <QtCore/QtCompare>
-#include <QtCore/QMetaObject>
+#include <QtCore/qcompare.h>
+#include <QtCore/qmetaobject.h>
 
 QT_FORWARD_DECLARE_CLASS(QDebug)
 
index 305095d1f97c2da1fea494efe8f9a20d6f4c4eec..a60dd33192a4a4a3de6ee245d178af51439dbdc4 100644 (file)
@@ -2,16 +2,17 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "feature_select.h"
-#include "pysidecleanup.h"
-#include "pysideqobject.h"
+#include "basewrapper.h"
 #include "pysidestaticstrings.h"
 #include "class_property.h"
 
-#include <shiboken.h>
+#include <autodecref.h>
 #include <sbkfeature_base.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
 #include <signature_p.h>
 
-#include <QtCore/QStringList>
+#include <QtCore/qstringlist.h>
 
 //////////////////////////////////////////////////////////////////////////////
 //
index 02cff066b22f73e7dd8ca0c79832a4e46774b110..195c000dc1b56f85c12c90717b509709fecd78e7 100644 (file)
 #include <basewrapper.h>
 #include <bindingmanager.h>
 #include <gilstate.h>
+#include <helper.h>
 #include <sbkconverter.h>
 #include <sbkstring.h>
 #include <sbkstaticstrings.h>
 #include <sbkfeature_base.h>
 #include <sbkmodule.h>
 
-#include <QtCore/QByteArray>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFileInfo>
-#include <QtCore/QMetaMethod>
-#include <QtCore/QMutex>
-#include <QtCore/QStack>
-#include <QtCore/QThread>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qthread.h>
 #include <QtCore/private/qobject_p.h>
 
 #include <algorithm>
@@ -55,7 +56,7 @@
 #ifdef Q_OS_WIN
 #  include <conio.h>
 #else
-#  include <QtCore/QDeadlineTimer>
+#  include <QtCore/qdeadlinetimer.h>
 #  include <QtCore/private/qcore_unix_p.h>
 #endif
 
@@ -660,7 +661,7 @@ PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *n
             }
             if (!signalList.isEmpty()) {
                 auto *pySignal = reinterpret_cast<PyObject *>(
-                    Signal::newObjectFromMethod(self, signalList));
+                    Signal::newObjectFromMethod(cppSelf, self, signalList));
                 PyObject_SetAttr(self, name, pySignal);
                 return pySignal;
             }
@@ -782,10 +783,7 @@ PyTypeObject *getTypeForQObject(const QObject *cppSelf)
     if (existing != nullptr)
         return reinterpret_cast<PyObject *>(existing)->ob_type;
     // Find the best match (will return a PySide type)
-    auto *sbkObjectType = Shiboken::ObjectType::typeForTypeName(typeName(cppSelf));
-    if (sbkObjectType != nullptr)
-        return reinterpret_cast<PyTypeObject *>(sbkObjectType);
-    return nullptr;
+    return Shiboken::ObjectType::typeForTypeName(typeName(cppSelf));
 }
 
 PyObject *getWrapperForQObject(QObject *cppSelf, PyTypeObject *sbk_type)
@@ -878,9 +876,7 @@ QString pyPathToQString(PyObject *path)
 
 bool isCompiledMethod(PyObject *callback)
 {
-    return PyObject_HasAttr(callback, PySide::PySideName::im_func())
-           && PyObject_HasAttr(callback, PySide::PySideName::im_self())
-           && PyObject_HasAttr(callback, PySide::PySideMagicName::code());
+    return Shiboken::isCompiledMethod(callback);
 }
 
 static const unsigned char qt_resource_name[] = {
@@ -930,7 +926,7 @@ bool registerInternalQtConf()
     // This will disable the internal qt.conf which points to the PySide6 subdirectory (due to the
     // subdirectory not existing anymore).
 #ifndef PYPY_VERSION
-    QString executablePath = QString::fromWCharArray(Py_GetProgramFullPath());
+    QString executablePath = QString::fromWCharArray(PyUnicode_AsWideCharString(PySys_GetObject("executable"), nullptr));
 #else
     // PYSIDE-535: FIXME: Add this function when available.
     QString executablePath = QLatin1StringView("missing Py_GetProgramFullPath");
index 0925a6bd6d2ff89eaf1c9b02ce5d17910045cebc..5ccd3fa677a6e2a2aa6ff7b16a4d78a9e52208c5 100644 (file)
@@ -9,9 +9,9 @@
 
 #include <pysidemacros.h>
 
-#include <QtCore/QList>
-#include <QtCore/QPoint>
-#include <QtCore/QPointF>
+#include <QtCore/qlist.h>
+#include <QtCore/qpoint.h>
+#include <QtCore/qpoint.h>
 
 namespace PySide::Numpy
 {
index 951cdd7ee71e869749512c2068cccd7c8113b6fa..79d0cbd35acda2ea060a6b79d669618d6a022c1f 100644 (file)
@@ -9,7 +9,7 @@
 #include <sbkpython.h>
 #include <pep384ext.h>
 
-#include <QtCore/QByteArray>
+#include <QtCore/qbytearray.h>
 
 #include <array>
 #include <string>
index db643df92bb45f992d292c5ca8f612597015d39b..085e8980e5410964a5b7cf503fd28df230828124 100644 (file)
@@ -8,8 +8,9 @@
 #include "pysideclassinfo_p.h"
 #include "dynamicqmetaobject.h"
 
-#include <shiboken.h>
 #include <signature.h>
+#include <sbktypefactory.h>
+#include <sbkstring.h>
 
 extern "C"
 {
index e048658293945ba1475f3f1c0e4fa3b7cab0bf37..e1cbfb3792dff94ac1f3e284298e0cb0bdbadbc2 100644 (file)
@@ -8,8 +8,8 @@
 
 #include <sbkpython.h>
 
-#include <QtCore/QByteArray>
-#include <QtCore/QList>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qlist.h>
 
 namespace PySide::ClassInfo {
 
index 4ef456f76c82824c4f322bf4d8db1a8904b2767c..1aed0bc5a1a81c85d45dad25e30479eb1ac5af27 100644 (file)
@@ -9,7 +9,7 @@
 #include "pysideclassdecorator_p.h"
 #include "pysideclassinfo.h"
 
-#include <QtCore/QMetaObject>
+#include <QtCore/qmetaobject.h>
 
 struct PySideClassInfo;
 
index bf167264dd850f9a96583f166c2bb9ce6f2c8bb2..99a51b11a3d05310463c7938095045412406db71 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef PYSIDE_LOGGING_P_H
 #define PYSIDE_LOGGING_P_H
 
-#include <QtCore/QLoggingCategory>
+#include <QtCore/qloggingcategory.h>
 
 Q_DECLARE_LOGGING_CATEGORY(lcPySide)
 
index 540e933ae8080b21bdd6527cb79aaf7f3a0aa458..7a496c4b7e42f05fd992e8686ef99951610274dc 100644 (file)
@@ -4,10 +4,17 @@
 #include "pysidemetafunction.h"
 #include "pysidemetafunction_p.h"
 
-#include <shiboken.h>
+#include <signalmanager.h>
+
+#include <autodecref.h>
+#include <basewrapper.h>
+#include <sbkconverter.h>
+#include <sbktypefactory.h>
 #include <signature.h>
 
-#include <QtCore/QMetaMethod>
+#include <QtCore/qmetaobject.h>
+
+using namespace Qt::StringLiterals;
 
 extern "C"
 {
@@ -161,6 +168,10 @@ bool call(QObject *self, int methodIndex, PyObject *args, PyObject **retVal)
                 QString tmp;
                 converter.toCpp(obj, &tmp);
                 methValues[i] = tmp;
+            } else if (metaType.id() == PyObjectWrapper::metaTypeId()) {
+                // Manual conversion, see PyObjectWrapper converter registration
+                methValues[i] = QVariant::fromValue(PyObjectWrapper(obj.object()));
+                methArgs[i] = methValues[i].data();
             } else {
                 converter.toCpp(obj, methArgs[i]);
             }
index 26a2cfd68a8b3eeef2c5fb3eff32834e7d320ed8..6aee49144bebd1db4ed00ee20bebacd1d02eb6b5 100644 (file)
@@ -8,7 +8,7 @@
 
 #include <sbkpython.h>
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 extern "C"
 {
index 7c04000510b16240bc359434d9b048a7a2b5fdb2..69d3470430db6394a8ba2a28a2350bab6b4bb9b5 100644 (file)
@@ -6,13 +6,20 @@
 #include "pysideproperty_p.h"
 #include "pysidesignal.h"
 #include "pysidesignal_p.h"
+#include "signalmanager.h"
 
-#include <shiboken.h>
+#include <autodecref.h>
 #include <pep384ext.h>
+#include <sbkconverter.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
+#include <sbktypefactory.h>
 #include <signature.h>
 
 using namespace Shiboken;
 
+using namespace Qt::StringLiterals;
+
 extern "C"
 {
 
@@ -144,16 +151,20 @@ void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, v
     switch (call) {
     case QMetaObject::ReadProperty: {
         AutoDecRef value(getValue(source));
-        auto *obValue = value.object();
-        if (obValue) {
-            Conversions::SpecificConverter converter(typeName);
-            if (converter) {
-                converter.toCpp(obValue, args[0]);
-            } else {
-                // PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`.
-                PyErr_SetObject(PyExc_StopIteration, obValue);
-            }
+        if (value.isNull())
+            return;
+        if (typeName == "PyObject"_ba) {
+            // Manual conversion, see PyObjectWrapper converter registration
+            auto *pw = reinterpret_cast<PySide::PyObjectWrapper *>(args[0]);
+            pw->reset(value.object());
+            return;
+        }
+        if (Conversions::SpecificConverter converter(typeName); converter) {
+            converter.toCpp(value.object(), args[0]);
+            return;
         }
+        // PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`.
+        PyErr_SetObject(PyExc_StopIteration, value.object());
     }
         break;
 
@@ -471,19 +482,19 @@ namespace PySide::Property {
 
 static const char *Property_SignatureStrings[] = {
     "PySide6.QtCore.Property(self,type:type,"
-        "fget:typing.Optional[typing.Callable[[typing.Any],typing.Any]],"
-        "fset:typing.Optional[typing.Callable[[typing.Any,typing.Any],None]],"
-        "freset:typing.Optional[typing.Callable[[typing.Any,typing.Any],None]],"
+        "fget:typing.Optional[collections.abc.Callable[[typing.Any],typing.Any]]=None,"
+        "fset:typing.Optional[collections.abc.Callable[[typing.Any,typing.Any],None]]=None,"
+        "freset:typing.Optional[collections.abc.Callable[[typing.Any,typing.Any],None]]=None,"
         "doc:str=None,"
-        "notify:typing.Optional[typing.Callable[[],None]],"
+        "notify:typing.Optional[collections.abc.Callable[[],None]]=None,"
         "designable:bool=True,scriptable:bool=True,"
         "stored:bool=True,user:bool=False,constant:bool=False,final:bool=False)",
-    "PySide6.QtCore.Property.deleter(self,fdel:typing.Callable[[typing.Any],None])->PySide6.QtCore.Property",
-    "PySide6.QtCore.Property.getter(self,fget:typing.Callable[[typing.Any],typing.Any])->PySide6.QtCore.Property",
-    "PySide6.QtCore.Property.read(self,fget:typing.Callable[[typing.Any],typing.Any])->PySide6.QtCore.Property",
-    "PySide6.QtCore.Property.setter(self,fset:typing.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.Property",
-    "PySide6.QtCore.Property.write(self,fset:typing.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.Property",
-    "PySide6.QtCore.Property.__call__(self, func:typing.Callable[...,typing.Any])->PySide6.QtCore.Property",
+    "PySide6.QtCore.Property.deleter(self,fdel:collections.abc.Callable[[typing.Any],None])->PySide6.QtCore.Property",
+    "PySide6.QtCore.Property.getter(self,fget:collections.abc.Callable[[typing.Any],typing.Any])->PySide6.QtCore.Property",
+    "PySide6.QtCore.Property.read(self,fget:collections.abc.Callable[[typing.Any],typing.Any])->PySide6.QtCore.Property",
+    "PySide6.QtCore.Property.setter(self,fset:collections.abc.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.Property",
+    "PySide6.QtCore.Property.write(self,fset:collections.abc.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.Property",
+    "PySide6.QtCore.Property.__call__(self, func:collections.abc.Callable[...,typing.Any])->PySide6.QtCore.Property",
     nullptr}; // Sentinel
 
 void init(PyObject *module)
index a572efe45635d6ab348cae214996306ccedc88a0..511f01f88049c42d86bebfeaf7776b2522367863 100644 (file)
@@ -8,7 +8,7 @@
 
 #include <sbkpython.h>
 
-#include <QtCore/QMetaObject>
+#include <QtCore/qmetaobject.h>
 
 class PySidePropertyPrivate;
 
index 7ff5c12372df7086500e23a6f7c221685f7e0ab2..4f68ed3a7317725b995e4f0f04712be90e28aff4 100644 (file)
@@ -9,9 +9,9 @@
 #include "pysideproperty.h"
 #include <pysidemacros.h>
 
-#include <QtCore/QByteArray>
+#include <QtCore/qbytearray.h>
 #include <QtCore/qtclasshelpermacros.h>
-#include <QtCore/QMetaObject>
+#include <QtCore/qmetaobject.h>
 
 struct PySideProperty;
 
index 897c9f4d0b778cbec335c5e88e2b6c12542db62f..e922c2d245c52d0f1de1aa7425f818636553a5f7 100644 (file)
@@ -1,12 +1,14 @@
 // Copyright (C) 2020 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
-#include <shiboken.h>
-
 #include "pysideqenum.h"
-#include "dynamicqmetaobject.h"
-#include "pyside_p.h"
 
+#include <autodecref.h>
+#include <sbkenum.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
+
+#include <map>
 
 ///////////////////////////////////////////////////////////////
 //
index c3483e63eb61bf2b10bcf3e9471a55ba2b1de96f..e97db07306e2790c430486fc63a9df79a6d3cd72 100644 (file)
@@ -4,7 +4,10 @@
 #ifndef PYSIDE_QENUM_H
 #define PYSIDE_QENUM_H
 
+#include <sbkpython.h>
+
 #include <pysidemacros.h>
+
 #include <vector>
 
 namespace PySide::QEnum {
index ae2d295f6aef7998e3368128087fad6ffa3787dc..c38211f7ae9d13c5a3f7dd3600487fcae136a4b3 100644 (file)
@@ -6,7 +6,7 @@
 
 #include <sbkpython.h>
 
-#include <QtCore/QHash>
+#include <QtCore/qhash.h>
 
 namespace PySide
 {
index 3782dc3bbfec17353c92972ce0747ae1220e16a2..cb80326ba608774d78c364a3166120000ce85b12 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef PYSIDEQMETATYPE_H
 #define PYSIDEQMETATYPE_H
 
-#include <QtCore/QMetaType>
+#include <QtCore/qmetatype.h>
 
 namespace PySide
 {
index 875369c27103b0054b8359c00a1af7abe1ec7584..045abfbc9466cc26bf40e3f7475ac518642cfe40 100644 (file)
@@ -7,7 +7,7 @@
 #include "pysidemacros.h"
 #include <sbkpython.h>
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 #include <QtCore/qobjectdefs.h>
 
 #include <memory>
index 1e9cd28f9126bfc91ac34ee951ffdd9f3cfbf935..93920dfeefbd5329ce02d4cee879e931d329f5a2 100644 (file)
@@ -7,21 +7,24 @@
 #include "pysideqobject.h"
 #include "pysideutils.h"
 #include "pysidestaticstrings.h"
-#include "pysideweakref.h"
+#include "qobjectconnect.h"
 #include "signalmanager.h"
 
-#include <shiboken.h>
-#include <sbkstaticstrings.h>
-
-#include <QtCore/QByteArray>
-#include <QtCore/QDebug>
-#include <QtCore/QHash>
-#include <QtCore/QObject>
-#include <QtCore/QMetaMethod>
-#include <QtCore/QMetaObject>
+#include <autodecref.h>
+#include <helper.h>
 #include <pep384ext.h>
-#include <signature.h>
+#include <sbkconverter.h>
 #include <sbkenum.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qmetaobject.h>
 
 #include <algorithm>
 #include <optional>
@@ -74,7 +77,17 @@ QDebug operator<<(QDebug debug, const PySideSignalInstancePrivate &d)
 
 static bool isSourceDeleted(const PySideSignalInstance *p)
 {
-    return p->d == nullptr || p->d->shared->deleted;
+    return p->d == nullptr || p->d->shared->source.isNull();
+}
+
+static inline QObject *sender(const PySideSignalInstance *p)
+{
+    return p->d != nullptr ? p->d->shared->source.data() : nullptr;
+}
+
+static inline QByteArray qSignalSignature(const PySideSignalInstance *p)
+{
+    return QT_SIGNAL_SENTINEL + p->d->signature;
 }
 
 static bool connection_Check(PyObject *o)
@@ -110,12 +123,21 @@ static std::optional<QByteArrayList> parseArgumentNames(PyObject *argArguments)
     return result;
 }
 
+static const char msgSourceDeleted[] = "Signal source has been deleted";
+static const char msgTargetSignalDeleted[] = "Target signal has been deleted";
+
+static SbkConverter *metaObjConnectionConverter()
+{
+    static SbkConverter *result = Shiboken::Conversions::getConverter("QMetaObject::Connection");
+    Q_ASSERT(result);
+    return result;
+}
+
 namespace PySide::Signal {
     static QByteArray buildSignature(const QByteArray &, const QByteArray &);
     static void instanceInitialize(PySideSignalInstance *, PyObject *, PySideSignal *,
                                    const PySideSignalInstanceSharedPtr &shared, int);
     static PySideSignalData::Signature parseSignature(PyObject *);
-    static PyObject *buildQtCompatible(const QByteArray &);
 } // PySide::Signal
 
 extern "C"
@@ -373,7 +395,6 @@ static void signalInstanceFree(void *vself)
             Py_DECREF(dataPvt->next);
             dataPvt->next = nullptr;
         }
-        dataPvt->shared->deleted = true;
         delete dataPvt;
         self->d = nullptr;
     }
@@ -404,7 +425,7 @@ static FunctionArgumentsResult extractFunctionArgumentsFromSlot(PyObject *slot)
         ret.objCode = reinterpret_cast<PepCodeObject *>(PyFunction_GET_CODE(ret.function));
         ret.functionName = PepFunction_GetName(ret.function);
 
-    } else if (PySide::isCompiledMethod(slot)) {
+    } else if (Shiboken::isCompiledMethod(slot)) {
         // PYSIDE-1523: PyFunction_Check and PyMethod_Check are not accepting compiled forms, we
         // just go by attributes.
         ret.isMethod = true;
@@ -517,68 +538,56 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject
         "O|O:SignalInstance", const_cast<char **>(kwlist), &slot, &type))
         return nullptr;
 
+    Qt::ConnectionType connectionType = Qt::AutoConnection;
+    if (type != nullptr && qstrcmp(Py_TYPE(type)->tp_name, "ConnectionType") == 0) {
+        static SbkConverter *connectionTypeConv =
+            Shiboken::Conversions::getConverter("Qt::ConnectionType");
+        Q_ASSERT(connectionTypeConv);
+        Shiboken::Conversions::pythonToCppCopy(connectionTypeConv, type, &connectionType);
+    }
+
     auto *source = reinterpret_cast<PySideSignalInstance *>(self);
     if (!source->d)
         return PyErr_Format(PyExc_RuntimeError, "cannot connect uninitialized SignalInstance");
-    if (isSourceDeleted(source))
-        return PyErr_Format(PyExc_RuntimeError, "Signal source has been deleted");
-
-    Shiboken::AutoDecRef pyArgs(PyList_New(0));
-
-    bool match = false;
-    if (Py_TYPE(slot) == PySideSignalInstance_TypeF()) {
-        PySideSignalInstance *sourceWalk = source;
 
-        //find best match
-        while (sourceWalk && !match) {
-            auto *targetWalk = reinterpret_cast<PySideSignalInstance *>(slot);
-            while (targetWalk && !match) {
+    if (Py_TYPE(slot) == PySideSignalInstance_TypeF()) { // Connect signal to signal
+        // find best match
+        for (PySideSignalInstance *sourceWalk = source; sourceWalk != nullptr; ) {
+            for (auto *targetWalk = reinterpret_cast<PySideSignalInstance *>(slot);
+                 targetWalk != nullptr; ) {
                 if (QMetaObject::checkConnectArgs(sourceWalk->d->signature,
                                                   targetWalk->d->signature)) {
-                    PyList_Append(pyArgs, sourceWalk->d->shared->source);
-                    Shiboken::AutoDecRef sourceSignature(PySide::Signal::buildQtCompatible(sourceWalk->d->signature));
-                    PyList_Append(pyArgs, sourceSignature);
-
-                    PyList_Append(pyArgs, targetWalk->d->shared->source);
-                    Shiboken::AutoDecRef targetSignature(PySide::Signal::buildQtCompatible(targetWalk->d->signature));
-                    PyList_Append(pyArgs, targetSignature);
-
-                    match = true;
+                    if (isSourceDeleted(sourceWalk))
+                        return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+                    if (isSourceDeleted(targetWalk))
+                        return PyErr_Format(PyExc_RuntimeError, msgTargetSignalDeleted);
+
+                    auto conn = PySide::qobjectConnect(sender(sourceWalk),
+                                                       qSignalSignature(sourceWalk).constData(),
+                                                       sender(targetWalk),
+                                                       qSignalSignature(targetWalk).constData(),
+                                                       connectionType);
+                    return Shiboken::Conversions::copyToPython(metaObjConnectionConverter(), &conn);
                 }
-                targetWalk = reinterpret_cast<PySideSignalInstance *>(targetWalk->d->next);
+                targetWalk = targetWalk->d->next;
             }
-            sourceWalk = reinterpret_cast<PySideSignalInstance *>(sourceWalk->d->next);
+            sourceWalk = sourceWalk->d->next;
         }
-    } else {
-        // Adding references to pyArgs
-        PyList_Append(pyArgs, source->d->shared->source);
-
-        // Check signature of the slot (method or function) to match signal
-        PySideSignalInstance *matchedSlot = findSignalInstanceForSlot(source, slot);
-        Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(matchedSlot->d->signature));
-        PyList_Append(pyArgs, signature);
-        PyList_Append(pyArgs, slot);
-        match = true;
+        return PyErr_Format(PyExc_RuntimeError, "Failed to connect signal %s.",
+                            source->d->signature.constData());
     }
 
-    if (type)
-        PyList_Append(pyArgs, type);
-
-    if (match) {
-        Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs));
-        Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->shared->source,
-                                                       PySide::PySideName::qtConnect()));
-        if (pyMethod.isNull())  // PYSIDE-79: check if pyMethod exists.
-            return PyErr_Format(PyExc_RuntimeError, "method 'connect' vanished!");
-        PyObject *result = PyObject_CallObject(pyMethod, tupleArgs);
-        if (connection_Check(result))
-            return result;
-        Py_XDECREF(result);
+    if (PyCallable_Check(slot) == 0) {
+        return PyErr_Format(PyExc_TypeError, "Expected signal or callable, got \"%s\"",
+                            Py_TYPE(slot)->tp_name);
     }
-    if (!PyErr_Occurred()) // PYSIDE-79: inverse the logic. A Null return needs an error.
-        PyErr_Format(PyExc_RuntimeError, "Failed to connect signal %s.",
-                     source->d->signature.constData());
-    return nullptr;
+    if (isSourceDeleted(source))
+        return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+    PySideSignalInstance *matchedSlot = findSignalInstanceForSlot(source, slot);
+    auto conn = PySide::qobjectConnectCallback(sender(source),
+                                               qSignalSignature(matchedSlot).constData(),
+                                               slot, connectionType);
+    return Shiboken::Conversions::copyToPython(metaObjConnectionConverter(), &conn);
 }
 
 static int argCountInSignature(const char *signature)
@@ -592,10 +601,8 @@ static PyObject *signalInstanceEmit(PyObject *self, PyObject *args)
     if (!source->d)
         return PyErr_Format(PyExc_RuntimeError, "cannot emit uninitialized SignalInstance");
 
-    // PYSIDE-2201: Check if the object has vanished meanwhile.
-    //              Tried to revive it without exception, but this gives problems.
     if (isSourceDeleted(source))
-        return PyErr_Format(PyExc_RuntimeError, "The SignalInstance object was already deleted");
+        return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
 
     Shiboken::AutoDecRef pyArgs(PyList_New(0));
     Py_ssize_t numArgsGiven = PySequence_Size(args);
@@ -619,17 +626,15 @@ static PyObject *signalInstanceEmit(PyObject *self, PyObject *args)
             }
         }
     }
-    Shiboken::AutoDecRef sourceSignature(PySide::Signal::buildQtCompatible(source->d->signature));
-
-    PyList_Append(pyArgs, sourceSignature);
-    for (Py_ssize_t i = 0, max = PyTuple_Size(args); i < max; i++)
-        PyList_Append(pyArgs, PyTuple_GetItem(args, i));
 
-    Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->shared->source,
-                                                   PySide::PySideName::qtEmit()));
-
-    Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs));
-    return PyObject_CallObject(pyMethod.object(), tupleArgs);
+    const bool ok = PySide::SignalManager::emitSignal(sender(source),
+                                                      qSignalSignature(source).constData(),
+                                                      args);
+    if (PyErr_Occurred() != nullptr)
+        return nullptr;
+    if (ok)
+        Py_RETURN_TRUE;
+    Py_RETURN_FALSE;
 }
 
 static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key)
@@ -686,46 +691,74 @@ static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args)
     if (PyTuple_Check(args) && PyTuple_Size(args))
         slot = PyTuple_GetItem(args, 0);
 
-    bool match = false;
-    if (Py_TYPE(slot) == PySideSignalInstance_TypeF()) {
+    if (Py_TYPE(slot) == PySideSignalInstance_TypeF()) { // Disconnect signal from signal
         auto *target = reinterpret_cast<PySideSignalInstance *>(slot);
-        if (QMetaObject::checkConnectArgs(source->d->signature, target->d->signature)) {
-            PyList_Append(pyArgs, source->d->shared->source);
-            Shiboken::AutoDecRef source_signature(PySide::Signal::buildQtCompatible(source->d->signature));
-            PyList_Append(pyArgs, source_signature);
-
-            PyList_Append(pyArgs, target->d->shared->source);
-            Shiboken::AutoDecRef target_signature(PySide::Signal::buildQtCompatible(target->d->signature));
-            PyList_Append(pyArgs, target_signature);
-            match = true;
+        if (!QMetaObject::checkConnectArgs(source->d->signature, target->d->signature)) {
+            warnDisconnectFailed(slot, source->d->signature);
+            Py_RETURN_FALSE;
         }
-    } else if (connection_Check(slot)) {
-        PyList_Append(pyArgs, slot);
-        match = true;
-    } else {
-        // try the matching signature, fall back to first
-        auto *matchedSlot = slot != Py_None ? findSignalInstanceForSlot(source, slot) : source;
-        PyList_Append(pyArgs, matchedSlot->d->shared->source);
-        Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(matchedSlot->d->signature));
-        PyList_Append(pyArgs, signature);
-
-        // disconnect all, so we need to use the c++ signature disconnect(qobj, signal, 0, 0)
-        if (slot == Py_None)
-            PyList_Append(pyArgs, slot);
-        PyList_Append(pyArgs, slot);
-        match = true;
+
+        if (isSourceDeleted(source))
+            return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+        if (isSourceDeleted(target))
+            return PyErr_Format(PyExc_RuntimeError, msgTargetSignalDeleted);
+
+        auto *qSender = sender(source);
+        const bool ok = qSender->disconnect(qSignalSignature(source).constData(),
+                                            sender(target),
+                                            qSignalSignature(target).constData());
+        if (ok)
+            Py_RETURN_TRUE;
+        warnDisconnectFailed(slot, source->d->signature);
+        Py_RETURN_FALSE;
     }
 
-    if (match) {
-        Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs));
-        Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->shared->source,
-                                                       PySide::PySideName::qtDisconnect()));
-        PyObject *result = PyObject_CallObject(pyMethod, tupleArgs);
-        if (result != Py_True)
-            warnDisconnectFailed(slot, source->d->signature);
-        return result;
+    if (slot == Py_None) { // disconnect all signal receivers by signature
+        if (isSourceDeleted(source))
+            return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+        const bool ok =
+            source->d->shared->source->disconnect(qSignalSignature(source).constData());
+        if (ok)
+            Py_RETURN_TRUE;
+        warnDisconnectFailed(slot, source->d->signature);
+        Py_RETURN_FALSE;
     }
 
+    if (connection_Check(slot)) { // disconnection by connection ID
+        QMetaObject::Connection conn;
+        Shiboken::Conversions::pythonToCppCopy(metaObjConnectionConverter(), slot, &conn);
+        if (conn && QObject::disconnect(conn))
+            Py_RETURN_TRUE;
+        warnDisconnectFailed(slot, source->d->signature);
+        Py_RETURN_FALSE;
+    }
+
+    // Disconnect callable
+    if (PyCallable_Check(slot) == 0) {
+        return PyErr_Format(PyExc_TypeError,
+                            "Expected signal, callable or connection id, got \"%s\"",
+                            Py_TYPE(slot)->tp_name);
+    }
+
+    // try the matching signature, fall back to first
+    auto *matchedSlot = slot != Py_None ? findSignalInstanceForSlot(source, slot) : source;
+    if (isSourceDeleted(matchedSlot))
+        return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+
+    const auto sourceSignature = qSignalSignature(matchedSlot);
+    // disconnect all, so we need to use the c++ signature disconnect(qobj, signal, 0, 0)
+    auto *qSender = sender(matchedSlot);
+    if (slot == Py_None) {
+        const bool ok = qSender->disconnect(sourceSignature.constData(), nullptr, nullptr);
+        if (ok)
+            Py_RETURN_TRUE;
+        warnDisconnectFailed(slot, source->d->signature);
+        Py_RETURN_FALSE;
+    }
+
+    const bool ok = PySide::qobjectDisconnectCallback(qSender, sourceSignature, slot);
+    if (ok)
+        Py_RETURN_TRUE;
     warnDisconnectFailed(slot, source->d->signature);
     Py_RETURN_FALSE;
 }
@@ -770,7 +803,7 @@ static PyObject *signalCall(PyObject *self, PyObject *args, PyObject *kw)
     Shiboken::AutoDecRef homonymousMethod(PepExt_Type_CallDescrGet(signal->homonymousMethod,
                                                                    nullptr, nullptr));
     if (PyCFunction_Check(homonymousMethod.object())
-            && (PyCFunction_GET_FLAGS(homonymousMethod.object()) & METH_STATIC))
+            && (PyCFunction_GetFlags(homonymousMethod.object()) & METH_STATIC))
         return PyObject_Call(homonymousMethod, args, kw);
 
     // Assumes homonymousMethod is not a static method.
@@ -807,7 +840,7 @@ static PyObject *_getHomonymousMethod(PySideSignalInstance *inst)
     // but walk through the whole mro to find a hidden method with the same name.
     auto signalName = inst->d->signalName;
     Shiboken::AutoDecRef name(Shiboken::String::fromCString(signalName));
-    auto *mro = Py_TYPE(inst->d->shared->source)->tp_mro;
+    auto *mro = inst->d->shared->sourceType->tp_mro;
     const Py_ssize_t n = PyTuple_Size(mro);
 
     for (Py_ssize_t idx = 0; idx < n; idx++) {
@@ -825,6 +858,8 @@ static PyObject *_getHomonymousMethod(PySideSignalInstance *inst)
 static PyObject *signalInstanceCall(PyObject *self, PyObject *args, PyObject *kw)
 {
     auto *PySideSignal = reinterpret_cast<PySideSignalInstance *>(self);
+    if (isSourceDeleted(PySideSignal))
+        return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
     auto *hom = _getHomonymousMethod(PySideSignal);
     if (!hom) {
         PyErr_Format(PyExc_TypeError, "native Qt signal instance '%s' is not callable",
@@ -832,8 +867,11 @@ static PyObject *signalInstanceCall(PyObject *self, PyObject *args, PyObject *kw
         return nullptr;
     }
 
-    Shiboken::AutoDecRef homonymousMethod(PepExt_Type_CallDescrGet(hom, PySideSignal->d->shared->source,
-                                                                   nullptr));
+    auto *source = PySide::getWrapperForQObject(sender(PySideSignal),
+                                                PySideSignal->d->shared->sourceType);
+    if (source == nullptr)
+        return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+    Shiboken::AutoDecRef homonymousMethod(PepExt_Type_CallDescrGet(hom, source, nullptr));
     return PyObject_Call(homonymousMethod, args, kw);
 }
 
@@ -935,7 +973,8 @@ void updateSourceObject(PyObject *source)
                     Shiboken::AutoDecRef signalInstance(reinterpret_cast<PyObject *>(inst));
                     auto *si = reinterpret_cast<PySideSignalInstance *>(signalInstance.object());
                     auto shared = std::make_shared<PySideSignalInstanceShared>();
-                    shared->source = source;
+                    shared->source = PySide::convertToQObject(source, false);
+                    shared->sourceType = Py_TYPE(source);
                     instanceInitialize(si, key, reinterpret_cast<PySideSignal *>(value),
                                        shared, 0);
                     if (PyDict_SetItem(dict, key, signalInstance) == -1)
@@ -1026,13 +1065,6 @@ static PySideSignalData::Signature parseSignature(PyObject *args)
     return result;
 }
 
-static void sourceGone(void *data)
-{
-    auto *ptr = reinterpret_cast<PySideSignalInstanceSharedPtr *>(data);
-    (*ptr)->deleted = true;
-    delete ptr;
-}
-
 static void instanceInitialize(PySideSignalInstance *self, PyObject *name,
                                PySideSignal *signal,
                                const PySideSignalInstanceSharedPtr &shared,
@@ -1041,13 +1073,6 @@ static void instanceInitialize(PySideSignalInstance *self, PyObject *name,
     self->d = new PySideSignalInstancePrivate;
     PySideSignalInstancePrivate *selfPvt = self->d;
     selfPvt->shared = shared;
-    // PYSIDE-2201: We have no reference to source. Let's take a weakref to get
-    //              notified when source gets deleted.
-    if (index == 0) {
-        auto *ptr = new PySideSignalInstanceSharedPtr(shared);
-        PySide::WeakRef::create(shared->source, sourceGone, ptr);
-    }
-
     selfPvt->next = nullptr;
     if (signal->data->signalName.isEmpty())
         signal->data->signalName = Shiboken::String::toCString(name);
@@ -1085,11 +1110,9 @@ PySideSignalInstance *initialize(PySideSignal *self, PyObject *name, PyObject *o
     PySideSignalInstance *instance = PyObject_New(PySideSignalInstance,
                                                   PySideSignalInstance_TypeF());
     auto shared = std::make_shared<PySideSignalInstanceShared>();
-    shared->source = object;
+    shared->source = PySide::convertToQObject(object, false);
+    shared->sourceType = Py_TYPE(object);
     instanceInitialize(instance, name, self, shared, 0);
-    auto *sbkObj = reinterpret_cast<SbkObject *>(object);
-    if (!Shiboken::Object::wasCreatedByPython(sbkObj))
-        Py_INCREF(object); // PYSIDE-79: this flag was crucial for a wrapper call.
     return instance;
 }
 
@@ -1111,12 +1134,14 @@ bool connect(PyObject *source, const char *signal, PyObject *callback)
     return result;
 }
 
-PySideSignalInstance *newObjectFromMethod(PyObject *source, const QList<QMetaMethod>& methodList)
+PySideSignalInstance *newObjectFromMethod(QObject *sourceQObject, PyObject *source,
+                                          const QList<QMetaMethod>& methodList)
 {
     PySideSignalInstance *root = nullptr;
     PySideSignalInstance *previous = nullptr;
     auto shared = std::make_shared<PySideSignalInstanceShared>();
-    shared->source = source;
+    shared->source = sourceQObject;
+    shared->sourceType = Py_TYPE(source);
     for (const QMetaMethod &m : methodList) {
         PySideSignalInstance *item = PyObject_New(PySideSignalInstance, PySideSignalInstance_TypeF());
         if (!root)
@@ -1159,12 +1184,6 @@ static bool compareSignals(const PySideSignalData::Signature &sig1,
     return sig1.signature.isEmpty() && !sig2.signature.isEmpty();
 }
 
-static PyObject *buildQtCompatible(const QByteArray &signature)
-{
-    const auto ba = QT_SIGNAL_SENTINEL + signature;
-    return Shiboken::String::fromStringAndSize(ba, ba.size());
-}
-
 void registerSignals(PyTypeObject *pyObj, const QMetaObject *metaObject)
 {
     using Signature = PySideSignalData::Signature;
@@ -1213,7 +1232,9 @@ void registerSignals(PyTypeObject *pyObj, const QMetaObject *metaObject)
 
 PyObject *getObject(PySideSignalInstance *signal)
 {
-    return signal->d->shared->source;
+    if (auto *qSender = sender(signal))
+        return getWrapperForQObject(qSender, signal->d->shared->sourceType);
+    return nullptr;
 }
 
 const char *getSignature(PySideSignalInstance *signal)
@@ -1266,10 +1287,9 @@ QByteArray getCallbackSignature(QMetaMethod signal, QObject *receiver,
         }
 #endif
     } else if (PyCFunction_Check(callback)) {
-        const auto *funcObj = reinterpret_cast<const PyCFunctionObject *>(callback);
-        functionName = PepCFunction_GET_NAMESTR(funcObj);
-        useSelf = PyCFunction_GET_SELF(funcObj) != nullptr ? 1 : 0;
-        const int flags = PyCFunction_GET_FLAGS(funcObj);
+        functionName = PepCFunction_GET_NAMESTR(callback);
+        useSelf = PyCFunction_GetSelf(callback) != nullptr ? 1 : 0;
+        const int flags = PyCFunction_GetFlags(callback);
 
         if (receiver) {
             // Search for signature on metaobject
@@ -1345,7 +1365,7 @@ QByteArray codeCallbackName(PyObject *callback, const QByteArray &funcName)
         return funcName + QByteArray::number(quint64(self), 16) + QByteArray::number(quint64(func), 16);
     }
     // PYSIDE-1523: Handle the compiled case.
-    if (PySide::isCompiledMethod(callback)) {
+    if (Shiboken::isCompiledMethod(callback)) {
         // Not retaining references inline with what PyMethod_GET_(SELF|FUNC) does.
         Shiboken::AutoDecRef self(PyObject_GetAttr(callback, PySide::PySideName::im_self()));
         Shiboken::AutoDecRef func(PyObject_GetAttr(callback, PySide::PySideName::im_func()));
index d1f3674700bc5143dbe2559aa51513fdeb701ee4..d4bebccae3f5c6216f35a08859046d40d8a8228b 100644 (file)
@@ -9,8 +9,8 @@
 #include <sbkpython.h>
 #include <basewrapper.h>
 
-#include <QtCore/QList>
-#include <QtCore/QMetaMethod>
+#include <QtCore/qlist.h>
+#include <QtCore/qmetaobject.h>
 
 QT_BEGIN_NAMESPACE
 struct QMetaObject;
@@ -63,7 +63,8 @@ PYSIDE_API void registerSignals(PyTypeObject *pyObj, const QMetaObject *metaObje
  * @param   methods a list of QMetaMethod wich contains the supported signature
  * @return  Return a new reference to PyObject* of type  PySideSignal
  **/
-PYSIDE_API PySideSignalInstance *newObjectFromMethod(PyObject *source, const QList<QMetaMethod> &methods);
+PYSIDE_API PySideSignalInstance *newObjectFromMethod(QObject *sourceQObject, PyObject *source,
+                                                     const QList<QMetaMethod> &methods);
 
 /**
  * This function initializes the Signal object by creating a PySideSignalInstance
index 259da0f3470a6c6440429f0de14be18db306ae77..627a838af26450ed4fc9d2f07f7864c0b2f52cc6 100644 (file)
@@ -6,8 +6,10 @@
 
 #include <sbkpython.h>
 
-#include <QtCore/QByteArray>
-#include <QtCore/QList>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qpointer.h>
 
 #include <memory>
 
@@ -40,8 +42,8 @@ extern "C"
 
 struct PySideSignalInstanceShared
 {
-    PyObject *source = nullptr;
-    bool deleted = false;
+    QPointer<QObject> source;
+    PyTypeObject *sourceType = nullptr;
 };
 
 using PySideSignalInstanceSharedPtr = std::shared_ptr<PySideSignalInstanceShared>;
index 5232b4e4c599286216969aa68b14c3618a29e835..bc034c724a87d278a3d249cc74eee0e87cddb144 100644 (file)
@@ -5,12 +5,16 @@
 #include "pysideslot_p.h"
 #include "pysidestaticstrings.h"
 
-#include <shiboken.h>
-
-#include <QtCore/QMetaObject>
-#include <QtCore/QString>
+#include <autodecref.h>
+#include <basewrapper.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
+#include <sbktypefactory.h>
 #include <signature.h>
 
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qstring.h>
+
 using namespace Shiboken;
 
 struct SlotData
@@ -168,7 +172,7 @@ DataList *dataListFromCapsule(PyObject *capsule)
 
 static const char *Slot_SignatureStrings[] = {
     "PySide6.QtCore.Slot(self,*types:type,name:str=nullptr,result:type=nullptr)",
-    "PySide6.QtCore.Slot.__call__(self,function:typing.Callable[[typing.Any],typing.Any])->typing.Any",
+    "PySide6.QtCore.Slot.__call__(self,function:collections.abc.Callable[...,typing.Any])->typing.Any",
     nullptr}; // Sentinel
 
 void init(PyObject *module)
index 47c2f2c1b972e4994325d79d3742f8f60644f6da..579e7f74c5f6d327f21026908e17427a1050e68e 100644 (file)
@@ -35,6 +35,8 @@ PYSIDE_API QString pyStringToQString(PyObject *str);
 /// Provide an efficient, correct PathLike interface.
 PYSIDE_API QString pyPathToQString(PyObject *path);
 
+/// Returns whether \a method is a compiled method (Nuitka).
+/// \sa Shiboken::isCompiledMethod()
 PYSIDE_API bool isCompiledMethod(PyObject *callback);
 
 struct debugPyTypeObject
index 48f9b3d032e08c52c3c852ca4a430c4399c1bb8c..c200aada6c45274561d15091699334fcbb54cf4c 100644 (file)
@@ -3,8 +3,8 @@
 
 #include "pysideweakref.h"
 
-#include <sbkpython.h>
-#include <shiboken.h>
+#include <basewrapper.h>
+#include <sbktypefactory.h>
 
 struct PySideCallableObject {
     PyObject_HEAD
index e29c73455679949ee515d84c13f7b2d2b0a27962..9ebf3f5d825578bafc94145575de91f2da7c7496 100644 (file)
@@ -1,8 +1,8 @@
 // Copyright (C) 2016 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
-#ifndef __PYSIDEWEAKREF__
-#define __PYSIDEWEAKREF__
+#ifndef PYSIDEWEAKREF_H
+#define PYSIDEWEAKREF_H
 
 #include <pysidemacros.h>
 #include <sbkpython.h>
@@ -15,4 +15,4 @@ PYSIDE_API PyObject* create(PyObject* ob, PySideWeakRefFunction func, void* user
 
 } // namespace PySide::WeakRef
 
-#endif
+#endif // PYSIDEWEAKREF_H
index b3b0633fafad60e6afe6a4ef1505743e6f6f1edd..3c862b3aaadd89853dee86133ec28fa4edc3a200 100644 (file)
 #include <sbkstaticstrings.h>
 #include "basewrapper.h"
 #include "autodecref.h"
+#include <helper.h>
 
-#include <QtCore/QDebug>
-#include <QtCore/QMetaMethod>
-#include <QtCore/QObject>
+#include <QtCore/qdebug.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qobject.h>
 
 #include <QtCore/private/qobject_p.h>
 
@@ -111,9 +112,9 @@ static GetReceiverResult getReceiver(QMetaMethod signal, PyObject *callback)
         result.receiver = PySide::convertToQObject(result.self, false);
 #endif
     } else if (PyCFunction_Check(callback)) {
-        result.self = PyCFunction_GET_SELF(callback);
+        result.self = PyCFunction_GetSelf(callback);
         result.receiver = PySide::convertToQObject(result.self, false);
-    } else if (PySide::isCompiledMethod(callback)) {
+    } else if (Shiboken::isCompiledMethod(callback)) {
         result.self = PyObject_GetAttr(callback, Shiboken::PyName::im_self());
         Py_DECREF(result.self);
         result.receiver = PySide::convertToQObject(result.self, false);
@@ -182,8 +183,10 @@ QMetaObject::Connection qobjectConnect(QObject *source, QMetaMethod signal,
                                        QObject *receiver, QMetaMethod slot,
                                        Qt::ConnectionType type)
 {
-    return qobjectConnect(source, signal.methodSignature().constData(),
-                          receiver, slot.methodSignature().constData(), type);
+    PySide::SignalManager::registerMetaMethod(receiver,
+                                              slot.methodSignature().constData(),
+                                              slot.methodType());
+    return QObject::connect(source, signal, receiver, slot, type);
 }
 
 QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *signal,
@@ -274,8 +277,15 @@ bool qobjectDisconnectCallback(QObject *source, const char *signal, PyObject *ca
     if (signalIndex == -1)
         return false;
 
-    if (!disconnectSlot(source, signalIndex, callback))
-        return false;
+    if (!disconnectSlot(source, signalIndex, callback)) {
+        // PYSIDE-3020: Check for disconnecting a string-based connection by passing a callable?
+        auto receiver = getReceiver(metaObject->method(signalIndex), callback);
+        if (receiver.receiver == nullptr || receiver.slotIndex == -1
+            || !QMetaObject::disconnect(source, signalIndex,
+                                        receiver.receiver, receiver.slotIndex)) {
+            return false;
+        }
+    }
 
     const QMetaMethod signalMethod = metaObject->method(signalIndex);
     static_cast<FriendlyQObject *>(source)->disconnectNotify(signalMethod);
index 66359ae672d613c959b2245705801ffc66800fb3..080990371f01271274ae447b2f74ee2bed0009e9 100644 (file)
@@ -8,7 +8,7 @@
 
 #include <sbkpython.h>
 
-#include <QtCore/QMetaObject>
+#include <QtCore/qmetaobject.h>
 
 QT_FORWARD_DECLARE_CLASS(QObject)
 QT_FORWARD_DECLARE_CLASS(QMetaMethod)
index fe99342920d524e864a91ca7a9d43883f51a12ad..a7bbcdf4ae64620b17c6c64ddd96d20d26387fd6 100644 (file)
 #include <sbkstaticstrings.h>
 #include <sbkerrors.h>
 
-#include <QtCore/QCoreApplication>
-#include <QtCore/QByteArrayView>
-#include <QtCore/QDebug>
-#include <QtCore/QHash>
-#include <QtCore/QScopedPointer>
-#include <QtCore/QTimerEvent>
-
+#include <QtCore/qbytearrayview.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qcoreevent.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qmetatype.h>
+#include <QtCore/qscopedpointer.h>
+
+#include <climits>
 #include <memory>
+#include <utility>
 
 using namespace Qt::StringLiterals;
 
@@ -36,6 +39,8 @@ using namespace Qt::StringLiterals;
 
 static PyObject *metaObjectAttr = nullptr;
 
+static int pyObjectWrapperMetaTypeId = QMetaType::UnknownType;
+
 static void destroyMetaObject(PyObject *obj)
 {
     void *ptr = PyCapsule_GetPointer(obj, nullptr);
@@ -125,6 +130,17 @@ PyObjectWrapper::PyObjectWrapper(const PyObjectWrapper &other)
     Py_XINCREF(m_me);
 }
 
+PyObjectWrapper::PyObjectWrapper(PyObjectWrapper &&other) noexcept
+ : m_me{std::exchange(other.m_me, nullptr)}
+{
+}
+
+PyObjectWrapper &PyObjectWrapper::operator=(PyObjectWrapper &&other) noexcept
+{
+    m_me = std::exchange(other.m_me, nullptr);
+    return *this;
+}
+
 PyObjectWrapper::~PyObjectWrapper()
 {
     // Check that Python is still initialized as sometimes this is called by a static destructor
@@ -156,6 +172,10 @@ PyObjectWrapper::operator PyObject *() const
     return m_me;
 }
 
+int PyObjectWrapper::metaTypeId()
+{
+    return pyObjectWrapperMetaTypeId;
+}
 
 int PyObjectWrapper::toInt() const
 {
@@ -221,7 +241,29 @@ QDataStream &operator>>(QDataStream &in, PyObjectWrapper &myObj)
     return in;
 }
 
-};
+PYSIDE_API QDebug operator<<(QDebug debug, const PyObjectWrapper &myObj)
+{
+    QDebugStateSaver saver(debug);
+    debug.noquote();
+    debug.nospace();
+    // Do not repeat the type name as it is typically called from the QVariant debug
+    // operator, which outputs the type.
+    debug << '<';
+    if (PyObject *ob = myObj) {
+        const auto refs = Py_REFCNT(ob);
+        debug << Py_TYPE(ob)->tp_name << " at " << ob;
+        if (refs == UINT_MAX) // _Py_IMMORTAL_REFCNT
+            debug << ", immortal";
+        else
+            debug << ", refs=" << refs;
+    } else {
+        debug << '0';
+    }
+    debug << '>';
+    return debug;
+}
+
+} // namespace PySide
 
 using namespace PySide;
 
@@ -238,19 +280,11 @@ struct SignalManagerPrivate
 SignalManager::QmlMetaCallErrorHandler
     SignalManagerPrivate::m_qmlMetaCallErrorHandler = nullptr;
 
-static void PyObject_PythonToCpp_PyObject_PTR(PyObject *pyIn, void *cppOut)
-{
-    *reinterpret_cast<PyObject **>(cppOut) = pyIn;
-}
-static PythonToCppFunc is_PyObject_PythonToCpp_PyObject_PTR_Convertible(PyObject * /* pyIn */)
+static PyObject *CopyCppToPythonPyObject(const void *cppIn)
 {
-    return PyObject_PythonToCpp_PyObject_PTR;
-}
-static PyObject *PyObject_PTR_CppToPython_PyObject(const void *cppIn)
-{
-    auto *pyOut = reinterpret_cast<PyObject *>(const_cast<void *>(cppIn));
-    if (pyOut)
-        Py_INCREF(pyOut);
+    const auto *wrapper = reinterpret_cast<const PyObjectWrapper *>(cppIn);
+    PyObject *pyOut = *wrapper;
+    Py_XINCREF(pyOut);
     return pyOut;
 }
 
@@ -260,13 +294,16 @@ void SignalManager::init()
     using namespace Shiboken;
 
     // Register PyObject type to use in queued signal and slot connections
-    qRegisterMetaType<PyObjectWrapper>("PyObject");
+    pyObjectWrapperMetaTypeId = qRegisterMetaType<PyObjectWrapper>("PyObject");
     // Register QVariant(enum) conversion to QVariant(int)
     QMetaType::registerConverter<PyObjectWrapper, int>(&PyObjectWrapper::toInt);
 
-    SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type, nullptr);
-    Shiboken::Conversions::setCppPointerToPythonFunction(converter, PyObject_PTR_CppToPython_PyObject);
-    Shiboken::Conversions::setPythonToCppPointerFunctions(converter, PyObject_PythonToCpp_PyObject_PTR, is_PyObject_PythonToCpp_PyObject_PTR_Convertible);
+    // Register a shiboken converter for PyObjectWrapper->Python (value conversion).
+    // Python->PyObjectWrapper is not registered since the converters do not work for
+    // non-SbkObject types (falling back to plain pointer pass through).
+    // This conversion needs to be done manually via QVariant.
+    SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type,
+                                                                     CopyCppToPythonPyObject);
     Shiboken::Conversions::registerConverterName(converter, "PyObject");
     Shiboken::Conversions::registerConverterName(converter, "object");
     Shiboken::Conversions::registerConverterName(converter, "PyObjectWrapper");
@@ -407,7 +444,7 @@ int SignalManagerPrivate::qtMethodMetacall(QObject *object,
     //         because the object can be deleted inside the called slot.
 
     if (gil == nullptr)
-        gil.reset(new Shiboken::GilState);
+        gil = std::make_unique<Shiboken::GilState>();
 
     if (PyErr_Occurred())
         handleMetaCallError(object, &result);
@@ -464,16 +501,21 @@ static int callPythonMetaMethodHelper(const QByteArrayList &paramTypes,
                                       const char *returnType /* = nullptr */,
                                       void **args, PyObject *pyCallable)
 {
+    using SpecificConverter = Shiboken::Conversions::SpecificConverter;
     const qsizetype argsSize = paramTypes.size();
     Shiboken::AutoDecRef preparedArgs(PyTuple_New(argsSize));
 
     for (qsizetype i = 0; i < argsSize; ++i) {
         void *data = args[i + 1];
-        auto param = paramTypes.at(i);
-        Shiboken::Conversions::SpecificConverter converter(param.constData());
+        const auto &param = paramTypes.at(i);
+        SpecificConverter converter(param.constData());
         if (!converter.isValid())
             return CallResult::CallArgumentError + int(i);
-        PyTuple_SetItem(preparedArgs, i, converter.toPython(data));
+        // Only pointer conversion available for const-ref - add indirection
+        const bool valueToPtr = converter.conversionType() == SpecificConverter::PointerConversion
+            && !param.endsWith('*') && param != "PyObject"_ba;
+        auto *src = valueToPtr ? &data : data;
+        PyTuple_SetItem(preparedArgs, i, converter.toPython(src));
     }
 
     QScopedPointer<Shiboken::Conversions::SpecificConverter> retConverter;
index ee39a3a8dd060a0934ca12e254d6ecdcf705f5c5..9fe56efc26dddffe2368b6fd1258048844efbe3d 100644 (file)
@@ -9,11 +9,12 @@
 #include <sbkpython.h>
 #include <shibokenmacros.h>
 
-#include <QtCore/QMetaMethod>
+#include <QtCore/qmetaobject.h>
 
 #include <optional>
 
 QT_FORWARD_DECLARE_CLASS(QDataStream)
+QT_FORWARD_DECLARE_CLASS(QDebug)
 
 namespace PySide
 {
@@ -22,13 +23,13 @@ namespace PySide
 class PYSIDE_API PyObjectWrapper
 {
 public:
-    PyObjectWrapper(PyObjectWrapper&&) = delete;
-    PyObjectWrapper& operator=(PyObjectWrapper &&) = delete;
 
     PyObjectWrapper();
     explicit PyObjectWrapper(PyObject* me);
     PyObjectWrapper(const PyObjectWrapper &other);
     PyObjectWrapper& operator=(const PyObjectWrapper &other);
+    PyObjectWrapper(PyObjectWrapper&&) noexcept;
+    PyObjectWrapper &operator=(PyObjectWrapper &&) noexcept;
 
     void reset(PyObject *o);
 
@@ -43,17 +44,21 @@ public:
     // The proper fix would be to associate PyObjectWrapper to the corresponding C++ Enum.
     int toInt() const;
 
+    static int metaTypeId();
+
 private:
     PyObject* m_me;
 };
 
 PYSIDE_API QDataStream &operator<<(QDataStream& out, const PyObjectWrapper& myObj);
 PYSIDE_API QDataStream &operator>>(QDataStream& in, PyObjectWrapper& myObj);
+PYSIDE_API QDebug operator<<(QDebug debug, const PyObjectWrapper &myObj);
 
 class PYSIDE_API SignalManager
 {
 public:
     Q_DISABLE_COPY_MOVE(SignalManager)
+    ~SignalManager() = default;
 
     using QmlMetaCallErrorHandler = std::optional<int>(*)(QObject *object);
 
index 1af8c02cf2ee0297d58ee5a6f32a8517d227f010..77a405fbe52a99637cdf0244f65aae2783685e63 100644 (file)
@@ -1,6 +1,8 @@
 # Copyright (C) 2023 The Qt Company Ltd.
 # SPDX-License-Identifier: BSD-3-Clause
 
+find_package(Qt6 COMPONENTS Core CorePrivate Qml QmlPrivate)
+
 set(libpysideqml_libraries Qt::Core Qt::CorePrivate Qt::Qml Qt::QmlPrivate)
 
 set(libpysideqml_HEADERS # installed below
index 3fe673fdf99669f783a14936b432c8d366a72fa7..d09a60be3ff71787671afc4ebbb1e935be9e2f8a 100644 (file)
@@ -10,8 +10,8 @@
 #include "pysideqmluncreatable.h"
 #include "pysideqmlmetacallerror_p.h"
 
-#include <QtQml/QQmlPropertyMap>
-#include <QtQml/QQmlComponent>
+#include <QtQml/qqmlpropertymap.h>
+#include <QtQml/qqmlcomponent.h>
 
 #include <signalmanager.h>
 
index da0a2afd450e1a67baeccbb813613cc4bfb9e64d..41d7dee97bfb2ff9f4908b05865186a761353261 100644 (file)
 #include <pyside_p.h>
 #include <pysideclassdecorator_p.h>
 
-#include <shiboken.h>
-#include <signature.h>
+#include <autodecref.h>
+#include <gilstate.h>
+#include <sbkconverter.h>
 #include <sbkstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
 
 #include <QtQml/qqml.h>
 
index 9953c3623be3713912f90259aae3c0c706d67555..e2a96b60ce9007d680d2ba690cd6d7a1bd57db24 100644 (file)
@@ -8,9 +8,12 @@
 #include <pyside_p.h>
 #include <pysideclassdecorator_p.h>
 
-#include <shiboken.h>
-#include <signature.h>
+#include <autodecref.h>
+#include <gilstate.h>
+#include <sbkconverter.h>
 #include <sbkstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
 
 #include <QtQml/qqml.h>
 
index 90136f0aacb7cdf1216ce435c80019247f6b14e9..ef8d7fdf0a67ba2b42295d2000cb65c9f29e4975 100644 (file)
@@ -7,11 +7,13 @@
 #include <signalmanager.h>
 #include <pysideclassdecorator_p.h>
 
-#include <shiboken.h>
-#include <signature.h>
+#include <gilstate.h>
+#include <sbkconverter.h>
 #include <sbkstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 // The QmlForeign decorator modifies QmlElement to create a different type
 // QmlElement.
index 447d1656400594bfe3aba9c283308ca7793112f9..5011fd613a74f7e1aa42b143db29f4c316fad7f5 100644 (file)
@@ -4,15 +4,19 @@
 #include "pysideqmllistproperty_p.h"
 #include "pysideqmlregistertype_p.h"
 
-#include <shiboken.h>
+#include <autodecref.h>
+#include <gilstate.h>
 #include <pep384ext.h>
+#include <sbkconverter.h>
 #include <signature.h>
+#include <sbkstring.h>
+#include <sbktypefactory.h>
 
 #include <pysideproperty.h>
 #include <pysideproperty_p.h>
 
-#include <QtCore/QObject>
-#include <QtQml/QQmlListProperty>
+#include <QtCore/qobject.h>
+#include <QtQml/qqmllist.h>
 
 // This is the user data we store in the property.
 class QmlListPropertyPrivate : public PySidePropertyPrivate
@@ -281,10 +285,10 @@ void QmlListPropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call,
 
 static const char *PropertyList_SignatureStrings[] = {
     "PySide6.QtQml.ListProperty(self,type:type,"
-        "append:typing.Optional[typing.Callable[...,typing.Any]]=None,"
-        "at:typing.Optional[typing.Callable[...,typing.Any]]=None,"
-        "clear:typing.Optional[typing.Callable[...,typing.Any]]=None,"
-        "count:typing.Optional[typing.Callable[...,typing.Any]]=None)",
+        "append:typing.Optional[collections.abc.Callable[...,typing.Any]]=None,"
+        "at:typing.Optional[collections.abc.Callable[...,typing.Any]]=None,"
+        "clear:typing.Optional[collections.abc.Callable[...,typing.Any]]=None,"
+        "count:typing.Optional[collections.abc.Callable[...,typing.Any]]=None)",
     nullptr // Sentinel
 };
 
index 7e7758114ef51f23bae0603c26c99e47dfc5c9ba..a3d2664c4eef0f9484a0fce91d1d54799f60ac80 100644 (file)
 #  undef copysign
 #endif
 
-#include <QtCore/QObject>
-#include <QtCore/QString>
+#include <QtCore/qobject.h>
+#include <QtCore/qstring.h>
 
-#include <QtQml/QQmlEngine>
-#include <QtQml/QQmlListProperty>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmllist.h>
 
 #if __has_include (<private/qv4engine_p.h>)
 #  define QML_PRIVATE_API_SUPPORT
index faf3e4116602abc7caf269364561fbfed6ac62fe..a0c05b384b710a77387d0f6714a833d2b2ae975c 100644 (file)
@@ -5,7 +5,7 @@
 #include <pysideclassdecorator_p.h>
 #include <pysideqmlregistertype_p.h>
 
-#include <shiboken.h>
+#include <sbktypefactory.h>
 #include <signature.h>
 
 class PySideQmlNamedElementPrivate : public PySide::ClassDecorator::StringDecoratorPrivate
index 5fa53bb7a694ac58961ec64c12632c8dd9776e68..b64a72f8ef5565b6904dd02aa19ddf622b5044df 100644 (file)
 #include <optional>
 
 // shiboken
-#include <shiboken.h>
+#include <autodecref.h>
+#include <sbkconverter.h>
 #include <sbkstring.h>
+#include <sbktypefactory.h>
 
 // pyside
-#include <pyside.h>
 #include <pysideqobject.h>
 #include <pysideclassinfo.h>
 #include <pyside_p.h>
 
-#include <QtCore/QMutex>
-#include <QtCore/QTypeRevision>
+#include <QtCore/qmutex.h>
+#include <QtCore/qtyperevision.h>
 
 #include <QtQml/qqml.h>
-#include <QtQml/QJSValue>
-#include <QtQml/QQmlListProperty>
+#include <QtQml/qjsvalue.h>
+#include <QtQml/qqmllist.h>
 #include <private/qqmlmetatype_p.h>
 #include <private/qmetaobjectbuilder_p.h>
 
index f11f92241635d026da49896a7e284a51379c52e5..1f26d9c561ec26a2e4c78cb6171b8ab16e2fe970 100644 (file)
@@ -6,7 +6,7 @@
 
 #include <sbkpython.h>
 
-#include <QtCore/QByteArray>
+#include <QtCore/qbytearray.h>
 
 PyTypeObject *qObjectType();
 
index f369f7400b51ebb02e7c681cee19b3dcf08ec041..d1d56efacd82ff8aa392bbf62f2c7bf0e17a1e56 100644 (file)
@@ -3,8 +3,8 @@
 
 #include "pysideqmltypeinfo_p.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QHash>
+#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
 
 #include <algorithm>
 
index 112e127a7ba386f9ee0474049426f37bc946b197..6ba72e3e28cffd02ff32fff079792782830eff9c 100644 (file)
@@ -6,8 +6,8 @@
 
 #include <sbkpython.h>
 
-#include <QtCore/QByteArray>
-#include <QtCore/QFlags>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qflags.h>
 
 #include <memory>
 
index 938b6a85882d6e90a60b95a1d0a4728d662b9afd..348d53d58021b6c5ae776938fde7c695c2315759 100644 (file)
@@ -5,9 +5,9 @@
 #include <pysideclassdecorator_p.h>
 #include <pysideclassinfo.h>
 
-#include <shiboken.h>
-#include <signature.h>
 #include <sbkcppstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
 
 #include <QtCore/qbytearray.h>
 #include <private/qmetaobjectbuilder_p.h>
index 8a8adb3c834cce94b6bfc3fb1689e90f3f586818..af17f5e9e6324343e2eb2e8976a53d718623565c 100644 (file)
@@ -6,7 +6,7 @@
 
 #include <sbkpython.h>
 
-#include <QtCore/QByteArray>
+#include <QtCore/qbytearray.h>
 
 QT_FORWARD_DECLARE_CLASS(QMetaObjectBuilder)
 
diff --git a/sources/pyside6/libpysideremoteobjects/CMakeLists.txt b/sources/pyside6/libpysideremoteobjects/CMakeLists.txt
new file mode 100644 (file)
index 0000000..4669fb6
--- /dev/null
@@ -0,0 +1,88 @@
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: BSD-3-Clause
+
+if (NOT CMAKE_MINIMUM_REQUIRED_VERSION)
+    cmake_minimum_required(VERSION 3.18)
+    cmake_policy(VERSION 3.18)
+endif()
+
+project(libpysideremoteobjects LANGUAGES CXX)
+
+if (NOT libpyside_SOURCE_DIR) # Building standalone
+    message(STATUS "Building standalone. Setting C++ standard and build type.")
+    set(CMAKE_CXX_STANDARD 17)
+    set(CMAKE_CXX_STANDARD_REQUIRED ON)
+    if(NOT CMAKE_BUILD_TYPE)
+        set(CMAKE_BUILD_TYPE Release)
+    endif()
+    find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
+    find_package(Shiboken6 REQUIRED)
+    find_package(libpyside REQUIRED)
+    get_target_property(pyside6_SOURCE_DIR PySide6::pyside6 INTERFACE_INCLUDE_DIRECTORIES)
+endif()
+
+find_package(Qt6 REQUIRED COMPONENTS Core RepParser RemoteObjects RemoteObjectsPrivate)
+
+set(libpysideremoteobjects_HEADERS
+    pysidecapsulemethod_p.h
+    pysidedynamicclass_p.h
+    pysidedynamiccommon_p.h
+    pysidedynamicenum_p.h
+    pysidedynamicpod_p.h
+    pysiderephandler_p.h
+)
+
+set(libpysideremoteobjects_SRC
+    pysiderephandler.cpp
+    pysidecapsulemethod.cpp
+    pysidedynamiccommon.cpp
+    pysidedynamicclass.cpp
+    pysidedynamicpod.cpp
+    pysidedynamicenum.cpp
+    ${libpysideremoteobjects_HEADERS}
+)
+
+list(GET Qt6RepParser_INCLUDE_DIRS 0 REPPARSER_DIR)
+
+include(QtTargetHelpers)
+include(QtTestHelpers)
+include(QtLalrHelpers)
+add_library(pyside6remoteobjects STATIC ${libpysideremoteobjects_SRC})
+
+target_include_directories(pyside6remoteobjects PRIVATE
+    ${REPPARSER_DIR}
+    ${Qt${QT_VERSION_MAJOR}Core_PRIVATE_INCLUDE_DIRS}
+    ${Qt${QT_MAJOR_VERSION}RemoteObjects_INCLUDE_DIRS}
+    ${Qt${QT_MAJOR_VERSION}RemoteObjects_PRIVATE_INCLUDE_DIRS}
+    ${pyside6_SOURCE_DIR}                     # Added internally by the create_pyside_module function
+    ${SHIBOKEN_INCLUDE_DIR}
+    ${libpyside_SOURCE_DIR}
+    ${SHIBOKEN_PYTHON_INCLUDE_DIR}
+    ${CMAKE_CURRENT_BINARY_DIR}               # Include the component-specific build directory
+)
+
+target_link_libraries(pyside6remoteobjects PRIVATE
+    Shiboken6::libshiboken                    # Added internally by the create_pyside_module function
+    Qt6::Core
+    Qt6::RemoteObjectsPrivate
+)
+
+qt_process_qlalr(
+    pyside6remoteobjects
+    "${REPPARSER_DIR}/parser.g"
+    ""
+)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D QT_NO_CAST_FROM_ASCII -D QT_NO_CAST_TO_ASCII")
+
+#
+# install stuff
+#
+
+install(FILES ${libpysideremoteobjects_HEADERS}
+        DESTINATION include/${BINDING_NAME}${pyside6remoteobjects_SUFFIX})
+
+install(TARGETS pyside6remoteobjects EXPORT PySide6RemoteObjectsTargets
+                           LIBRARY DESTINATION "${LIB_INSTALL_DIR}"
+                           ARCHIVE DESTINATION "${LIB_INSTALL_DIR}"
+                           RUNTIME DESTINATION bin)
diff --git a/sources/pyside6/libpysideremoteobjects/pysidecapsulemethod.cpp b/sources/pyside6/libpysideremoteobjects/pysidecapsulemethod.cpp
new file mode 100644 (file)
index 0000000..d5a5454
--- /dev/null
@@ -0,0 +1,230 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysidecapsulemethod_p.h"
+
+extern "C"
+{
+
+// This struct is used for both CapsuleMethod and CapsuleProperty
+struct CapsuleDescriptor
+{
+    PyTypeObject base;
+    PyObject *capsule;
+    PyMethodDef methodDef;
+
+    void configure(PyObject *capsule, PyMethodDef *method)
+    {
+        this->capsule = capsule;
+        Py_INCREF(capsule);
+        // We make a copy of the input name and doc strings so they can be temporary on
+        // the input.
+        if (method->ml_name)
+            methodDef.ml_name = strdup(method->ml_name);
+        if (method->ml_doc)
+            methodDef.ml_doc = strdup(method->ml_doc);
+        methodDef.ml_meth = method->ml_meth;
+        methodDef.ml_flags = method->ml_flags;
+    }
+};
+
+static PyObject *CapsuleDescriptor_tp_new(PyTypeObject *type, PyObject * /* args */, PyObject * /* kwds */);
+static void CapsuleDescriptor_free(PyObject *self);
+static PyObject *CapsuleMethod_descr_get(PyObject *self, PyObject *instance, PyObject * /* owner */);
+static PyObject *CapsuleProperty_descr_get(PyObject *self, PyObject *instance, PyObject * /* owner */);
+static int CapsuleProperty_descr_set(PyObject *self, PyObject *instance, PyObject * /* owner */);
+
+/**
+ * We are creating two related types, CapsuleMethod and CapsuleProperty, that are
+ * used to enable lambda-like behavior. The difference is in usage, where
+ * CapsuleMethod's __get__ function returns a Callable (i.e., method-like usage:
+ * obj.capsuleMethodName(args)) and only supports the __get__ method.
+ * CapsuleProperty on the other hand is used for properties, and supports both
+ * __get__ and __set__ methods (i.e., obj.capsulePropertyName = value or val =
+ * obj.capsulePropertyName).
+ */
+static PyTypeObject *createCapsuleMethodType()
+{
+    PyType_Slot CapsuleMethodType_slots[] = {
+        {Py_tp_new,         reinterpret_cast<void *>(CapsuleDescriptor_tp_new)},
+        {Py_tp_descr_get,   reinterpret_cast<void *>(CapsuleMethod_descr_get)},
+        {Py_tp_free,        reinterpret_cast<void *>(CapsuleDescriptor_free)},
+        {0, nullptr}
+    };
+
+    PyType_Spec CapsuleMethodType_spec = {
+        "2:PySide6.QtRemoteObjects.CapsuleMethod",
+        sizeof(CapsuleDescriptor),
+        0,
+        Py_TPFLAGS_DEFAULT,
+        CapsuleMethodType_slots};
+
+    PyObject *type = PyType_FromSpec(&CapsuleMethodType_spec);
+    if (!type) {
+        PyErr_Print();
+        return nullptr;
+    }
+    return reinterpret_cast<PyTypeObject*>(type);
+}
+
+PyTypeObject *CapsuleMethod_TypeF(void)
+{
+    static auto *type = createCapsuleMethodType();
+    return type;
+}
+
+static PyTypeObject *createCapsulePropertyType(bool isWritable)
+{
+    PyType_Slot WritablePropertyType_slots[] = {
+        {Py_tp_new,         reinterpret_cast<void *>(CapsuleDescriptor_tp_new)},
+        {Py_tp_descr_get,   reinterpret_cast<void *>(CapsuleProperty_descr_get)},
+        {Py_tp_descr_set,   reinterpret_cast<void *>(CapsuleProperty_descr_set)},
+        {Py_tp_free,        reinterpret_cast<void *>(CapsuleDescriptor_free)},
+        {0, nullptr}
+    };
+
+    PyType_Slot ReadOnlyPropertyType_slots[] = {
+        {Py_tp_new,         reinterpret_cast<void *>(CapsuleDescriptor_tp_new)},
+        {Py_tp_descr_get,   reinterpret_cast<void *>(CapsuleProperty_descr_get)},
+        {Py_tp_free,        reinterpret_cast<void *>(CapsuleDescriptor_free)},
+        {0, nullptr}
+    };
+
+    PyType_Spec CapsulePropertyType_spec = {
+        "2:PySide6.QtRemoteObjects.CapsuleProperty",
+        sizeof(CapsuleDescriptor),
+        0,
+        Py_TPFLAGS_DEFAULT,
+        isWritable ? WritablePropertyType_slots : ReadOnlyPropertyType_slots};
+
+    PyObject *type = PyType_FromSpec(&CapsulePropertyType_spec);
+    if (!type) {
+        PyErr_Print();
+        return nullptr;
+    }
+    return reinterpret_cast<PyTypeObject*>(type);
+}
+
+PyTypeObject *CapsuleProperty_TypeF(bool isWritable=false)
+{
+    if (isWritable) {
+        static auto *type = createCapsulePropertyType(true);
+        return type;
+    }
+    static auto *type = createCapsulePropertyType(false);
+    return type;
+}
+
+static PyObject *CapsuleDescriptor_tp_new(PyTypeObject *type, PyObject * /* args */, PyObject * /* kwds */)
+{
+    auto *self = reinterpret_cast<CapsuleDescriptor *>(PyType_GenericAlloc(type, 0));
+    if (self != nullptr) {
+        self->capsule = nullptr;
+        self->methodDef = {nullptr, nullptr, METH_NOARGS, nullptr}; // Initialize methodDef
+    }
+    return reinterpret_cast<PyObject *>(self);
+}
+
+static void CapsuleDescriptor_free(PyObject *self)
+{
+    auto *d = reinterpret_cast<CapsuleDescriptor *>(self);
+    Py_XDECREF(d->capsule);
+    free(const_cast<char*>(d->methodDef.ml_name));
+    free(const_cast<char*>(d->methodDef.ml_doc));
+}
+
+static PyObject *CapsuleMethod_descr_get(PyObject *self, PyObject *instance, PyObject * /* owner */)
+{
+    if (instance == nullptr) {
+        // Return the descriptor object if accessed from the class
+        Py_INCREF(self);
+        return self;
+    }
+
+    auto *d = reinterpret_cast<CapsuleDescriptor *>(self);
+    CapsuleDescriptorData *data = new CapsuleDescriptorData{instance, d->capsule};
+    PyObject *payload = PyCapsule_New(data, "Payload", [](PyObject *capsule) {
+        delete reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(capsule, "Payload"));
+    });
+    if (!payload)
+        return nullptr;
+
+    Py_INCREF(payload);
+    return PyCFunction_New(&d->methodDef, payload);
+}
+
+bool add_capsule_method_to_type(PyTypeObject *type, PyMethodDef *method, PyObject *capsule)
+{
+    if (PyType_Ready(type) < 0) {
+        PyErr_Print();
+        return false;
+    }
+    auto *descriptor = reinterpret_cast<CapsuleDescriptor *>(
+        PyObject_CallObject(reinterpret_cast<PyObject *>(CapsuleMethod_TypeF()), nullptr));
+    if (!descriptor) {
+        PyErr_Print();
+        return false;
+    }
+    descriptor->configure(capsule, method);
+
+    auto *descr = reinterpret_cast<PyObject *>(descriptor);
+    if (PyObject_SetAttrString(reinterpret_cast<PyObject *>(type), method->ml_name, descr) < 0) {
+        PyErr_Print();
+        return false;
+    }
+    return true;
+}
+
+static PyObject *CapsuleProperty_descr_get(PyObject *self, PyObject *instance, PyObject * /* owner */)
+{
+    if (instance == nullptr) {
+        // Return the descriptor object if accessed from the class
+        Py_INCREF(self);
+        return self;
+    }
+
+    auto *d = reinterpret_cast<CapsuleDescriptor *>(self);
+    CapsuleDescriptorData *data = new CapsuleDescriptorData{instance, d->capsule};
+    PyObject *payload = PyCapsule_New(data, "Payload", [](PyObject *capsule) {
+        delete reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(capsule, "Payload"));
+    });
+    if (!payload)
+        return nullptr;
+
+    return PyObject_CallFunctionObjArgs(PyCFunction_New(&d->methodDef, payload), nullptr);
+}
+
+static int CapsuleProperty_descr_set(PyObject *self, PyObject *instance, PyObject *value)
+{
+    auto *d = reinterpret_cast<CapsuleDescriptor *>(self);
+    CapsuleDescriptorData *data = new CapsuleDescriptorData{instance, d->capsule};
+    PyObject *payload = PyCapsule_New(data, "Payload", [](PyObject *capsule) {
+        delete reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(capsule, "Payload"));
+    });
+    if (!payload)
+        return -1;
+
+    Py_INCREF(payload);
+    PyObject *result = PyObject_CallFunctionObjArgs(PyCFunction_New(&d->methodDef, payload),
+                                                    value, nullptr);
+    if (!result)
+        return -1;
+
+    Py_DECREF(result);
+    return 0;
+}
+
+// Returns a new CapsuleProperty descriptor object for use with PySideProperty
+PyObject *make_capsule_property(PyMethodDef *method, PyObject *capsule, bool isWritable)
+{
+    auto *type = CapsuleProperty_TypeF(isWritable);
+    auto *descriptor = PyObject_CallObject(reinterpret_cast<PyObject *>(type), nullptr);
+    if (!descriptor)
+        return nullptr;
+
+    reinterpret_cast<CapsuleDescriptor*>(descriptor)->configure(capsule, method);
+
+    return descriptor;
+}
+
+} // extern "C"
diff --git a/sources/pyside6/libpysideremoteobjects/pysidecapsulemethod_p.h b/sources/pyside6/libpysideremoteobjects/pysidecapsulemethod_p.h
new file mode 100644 (file)
index 0000000..7b6abc5
--- /dev/null
@@ -0,0 +1,87 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_CAPSULEMETHOD_P_H
+#define PYSIDE_CAPSULEMETHOD_P_H
+
+#include <sbkpython.h>
+
+extern "C"
+{
+
+/**
+ * This code is needed to solve, in C++ and adhering to the stable API,
+ * creating what are in effect lambda functions as instance methods on custom
+ * types. The goal is to be able to add methods to a dynamic type. If the .rep
+ * file defines a slot `mySlot`, it need to be added to the dynamic type. For
+ * Source types, this should be an abstract method that raises a
+ * NotImplementedError unless defined in the Python subclass. For Replica
+ * types, this should include an implementation that forwards the request
+ * through the underlying QRemoteObjectReplica instance.
+ *
+ * The stable API doesn't currently provide a way define a method that can
+ * receive both the `self`, `args`, and runtime (but constant per method, i.e.,
+ * lambda like) data using Py_tp_methods. Possibly post 3.13 when METH_METHOD is
+ * part of the stable API. But for now, it is not.
+ *
+ * The solution is to create a custom descriptor
+ * (https://docs.python.org/3/howto/descriptor.html) that can hold the runtime
+ * data and then when called, will return a PyCFunction_New generated PyObject
+ * that is passed both class instance `self` and the runtime data (a PyCapsule)
+ * together as a tuple as a new `self` for the method. The static method
+ * definition needs to expect and handle this, but when combined in C++, we can
+ * define a single handler that receives both the original `self` of the instance
+ * and the runtime capsule with data for handling.
+ */
+
+/**
+  * The CapsuleDescriptorData struct is what will be passed as the pseudo `self`
+  * from a CapsuleMethod or CapsuleProperty to the associated handler method. The
+  * handler method (which should look like a standard PyMethodDef method) should
+  * parse it into the payload (the "lambda variables") and the actual instance
+  * (the "self").
+  */
+struct CapsuleDescriptorData
+{
+    PyObject *self;
+    PyObject *payload;
+};
+
+/**
+ * The new type defining a descriptor that stores a PyCapsule.  This is used to
+ * store the runtime data, with the __get__ method returning a new Callable.
+ */
+PyTypeObject *CapsuleMethod_TypeF(void);
+
+/**
+ * The new type defining a descriptor that stores a PyCapsule.  This is used to
+ * store the runtime data, with the __get__ (and __set__ if isWritable) providing
+ * property behavior.
+ */
+PyTypeObject *CapsuleProperty_TypeF(bool isWritable);
+
+/**
+ * Add a capsule method (a descriptor) to a type.  This will create a new capsule
+ * method descriptor and add it as an attribute to the type, using the given name.
+ *
+ * A single handle can then respond to what appear to be distinct methods on the
+ * type, but using the runtime data (from the capsule) when handling each call.
+ *
+ * @param type The type to attach the created descriptor to.
+ * @param method The method definition to associate with the descriptor.
+ *               The name of the method will be used as the attribute name.
+ * @param capsule The capsule to store in the descriptor.
+ * @return True if the descriptor was added successfully, false otherwise.
+ */
+bool add_capsule_method_to_type(PyTypeObject *type, PyMethodDef *method,
+                                PyObject *capsule);
+
+/**
+ * Make a new CapsuleProperty type.
+ */
+PyObject *make_capsule_property(PyMethodDef *method, PyObject *capsule,
+                                bool isWritable = false);
+
+} // extern "C"
+
+#endif // PYSIDE_CAPSULEMETHOD_P_H
diff --git a/sources/pyside6/libpysideremoteobjects/pysidedynamicclass.cpp b/sources/pyside6/libpysideremoteobjects/pysidedynamicclass.cpp
new file mode 100644 (file)
index 0000000..941e38c
--- /dev/null
@@ -0,0 +1,506 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+// Workaround to access protected functions. PySide builds with this, but
+// since this is now a separate library, we need to add it too.
+
+#include "pysidedynamiccommon_p.h"
+#include "pysidedynamicclass_p.h"
+#include "pysidecapsulemethod_p.h"
+#include "pysiderephandler_p.h"
+
+#include <basewrapper.h>
+#include <sbkconverter.h>
+#include <sbkstring.h>
+
+#include <pyside_p.h>
+#include <pysideproperty.h>
+#include <pysideqobject.h>
+#include <pysidesignal.h>
+#include <pysideutils.h>
+
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qvariantlist.h>
+
+#include <QtRemoteObjects/qremoteobjectpendingcall.h>
+#include <QtRemoteObjects/qremoteobjectreplica.h>
+
+using namespace Shiboken;
+
+class FriendlyReplica : public QRemoteObjectReplica
+{
+public:
+    using QRemoteObjectReplica::send;
+    using QRemoteObjectReplica::setProperties;
+    using QRemoteObjectReplica::propAsVariant;
+    using QRemoteObjectReplica::sendWithReply;
+};
+
+extern "C"
+{
+
+PyObject *propertiesAttr()
+{
+    static PyObject *const s = Shiboken::String::createStaticString("__PROPERTIES__");
+    return s;
+}
+
+struct SourceDefs
+{
+    static PyTypeObject *getSbkType()
+    {
+        static PyTypeObject *sbkType =
+            Shiboken::Conversions::getPythonTypeObject("QObject");
+        return sbkType;
+    }
+
+    static PyObject *getBases()
+    {
+        static PyObject *bases = PyTuple_Pack(1, getSbkType());
+        return bases;
+    }
+
+    static const char *getTypePrefix()
+    {
+        return "2:PySide6.QtRemoteObjects.DynamicSource.";
+    }
+
+    static int tp_init(PyObject *self, PyObject *args, PyObject *kwds)
+    {
+        static initproc initFunc = reinterpret_cast<initproc>(PepType_GetSlot(getSbkType(), Py_tp_init));
+        int res = initFunc(self, args, kwds);
+        if (res < 0) {
+            PyErr_Print();
+            return res;
+        }
+
+        // Get the properties from the type
+        PyTypeObject *type = Py_TYPE(self);
+        auto *pyProperties = PyObject_GetAttr(reinterpret_cast<PyObject *>(type), propertiesAttr());
+        if (!pyProperties) {
+            PyErr_SetString(PyExc_RuntimeError, "Failed to get properties from type");
+            return -1;
+        }
+        // Add a copy of the properties to the object
+        auto *propPtr = reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(pyProperties, nullptr));
+        auto *propertiesCopy = new QVariantList(*propPtr);
+        PyObject *capsule = PyCapsule_New(propertiesCopy, nullptr, [](PyObject *capsule) {
+                delete reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(capsule, nullptr));
+            });
+        PyObject_SetAttr(self, propertiesAttr(), capsule);
+        Py_DECREF(capsule);
+        return res;
+    }
+
+    static PyObject *capsule_method_handler(PyObject *payload, PyObject *args)
+    {
+        auto *methodData = reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(payload,
+                                                                                          "Payload"));
+        if (!methodData) {
+            PyErr_SetString(PyExc_RuntimeError, "Invalid call to dynamic method.  Missing payload.");
+            return nullptr;
+        }
+        PyObject *self = methodData->self;
+        if (PyCapsule_IsValid(methodData->payload, "PropertyCapsule")) {
+            // Handle property getter/setter against our hidden properties attribute
+            auto *capsule = PyCapsule_GetPointer(methodData->payload, "PropertyCapsule");
+            if (capsule) {
+                auto *ob_dict = SbkObject_GetDict_NoRef(self);
+                auto *propPtr = PyCapsule_GetPointer(PyDict_GetItem(ob_dict, propertiesAttr()),
+                                                     nullptr);
+                auto *currentProperties = reinterpret_cast<QVariantList *>(propPtr);
+                auto *callData = reinterpret_cast<PropertyCapsule *>(capsule);
+                if (callData->indexInObject < 0
+                    || callData->indexInObject >= currentProperties->size()) {
+                    PyErr_Format(PyExc_RuntimeError, "Unknown property method: %s",
+                                 callData->name.constData());
+                    return nullptr;
+                }
+                const QVariant &currentVariant = currentProperties->at(callData->indexInObject);
+
+                // Handle getter
+                if (PyTuple_Size(args) == 0)
+                    return toPython(currentVariant);
+
+                // Handle setter
+                if (PyTuple_Size(args) != 1) {
+                    PyErr_SetString(PyExc_TypeError, "Property setter takes exactly one argument");
+                    return nullptr;
+                }
+                Conversions::SpecificConverter converter(currentVariant.metaType().name());
+                QVariant variant{currentVariant.metaType()};
+                auto metaType = currentVariant.metaType();
+                if (metaType.flags().testFlag(QMetaType::IsEnumeration)) {
+                    converter.toCpp(PyTuple_GetItem(args, 0), variant.data());
+                    variant.convert(metaType);
+                } else {
+                    converter.toCpp(PyTuple_GetItem(args, 0), variant.data());
+                }
+                if (PyErr_Occurred()) // POD conversion can produce an error
+                    return nullptr;
+                if (variant == currentVariant)
+                    Py_RETURN_NONE;
+
+                currentProperties->replace(callData->indexInObject, variant);
+                // Get the QMetaObject and emit the property changed signal if there is one
+                const auto *metaObject = PySide::retrieveMetaObject(self);
+                auto metaProperty = metaObject->property(callData->propertyIndex);
+                if (metaProperty.hasNotifySignal()) {
+                    // We know our custom types don't have multiple cpp objects
+                    void *cptr = reinterpret_cast<SbkObject *>(self)->d->cptr[0];
+                    auto *qObject = reinterpret_cast<QObject *>(cptr);
+                    void *_args[] = {nullptr, variant.data()};
+                    QMetaObject::activate(qObject, metaProperty.notifySignalIndex(), _args);
+                }
+                Py_RETURN_NONE;
+            }
+        }
+        if (PyCapsule_IsValid(methodData->payload, "MethodCapsule")) {
+            auto *capsule = PyCapsule_GetPointer(methodData->payload, "MethodCapsule");
+            auto *callData = reinterpret_cast<MethodCapsule *>(capsule);
+            if (callData->name.startsWith("push") && callData->name.size() > 4) {
+                const auto *metaObject = PySide::retrieveMetaObject(self);
+                // The convention for QtRO is if a property is named "something" and uses
+                // push, the name of the push method will be "pushSomething". But it is
+                // possible the name would be "Something", so we need to check upper
+                // and lower case.
+                auto name = callData->name.sliced(4);
+                auto index = metaObject->indexOfProperty(name.constData());
+                if (index < 0) {
+                    name[0] = tolower(name[0]);  // Try lower case
+                    index = metaObject->indexOfProperty(name.constData());
+                }
+                // It is possible a .rep names a Slot "push" or "pushSomething" that
+                // isn't generated for a property. Let that fall through to regular
+                // method handling.
+                if (index >= 0) {
+                    // Call the custom descriptor's set method
+                    auto result = PyObject_SetAttrString(self, name.constData(),
+                                                         PyTuple_GetItem(args, 0));
+                    if (result < 0) {
+                        PyErr_Print();
+                        return nullptr;
+                    }
+                    Py_RETURN_NONE;
+                }
+            }
+            // TODO: This doesn't do much, as it is "eaten" by a PyError_Print in
+            // SignalManager::handleMetaCallError()
+            // Is there a better way to address slots that need to be implemented?
+            PyErr_Format(PyExc_NotImplementedError, "** The method %s is not implemented",
+                         callData->name.constData());
+            return nullptr;
+        }
+
+        PyErr_SetString(PyExc_RuntimeError, "Unknown capsule type");
+        return nullptr;
+    }
+};
+
+struct ReplicaDefs
+{
+    static PyTypeObject *getSbkType()
+    {
+        static PyTypeObject *sbkType =
+            Shiboken::Conversions::getPythonTypeObject("QRemoteObjectReplica");
+        return sbkType;
+    }
+
+    static PyObject *getBases()
+    {
+        static PyObject *bases = PyTuple_Pack(1, getSbkType());
+        return bases;
+    }
+
+    static const char *getTypePrefix()
+    {
+        return "2:PySide6.QtRemoteObjects.DynamicReplica.";
+    }
+
+    static int tp_init(PyObject *self, PyObject *args, PyObject *kwds)
+    {
+        static initproc initFunc = reinterpret_cast<initproc>(PepType_GetSlot(getSbkType(),
+                                                                              Py_tp_init));
+        QRemoteObjectReplica *replica = nullptr;
+        if (PyTuple_Size(args) == 0) {
+            if (initFunc(self, args, kwds) < 0)
+                return -1;
+            Shiboken::Conversions::pythonToCppPointer(getSbkType(), self, &replica);
+        } else {  // Process replica with arguments passed from the added node.acquire method
+            PyObject *node = nullptr;
+            PyObject *constructorType = nullptr;
+            PyObject *name = nullptr;
+            static PyTypeObject *nodeType = Shiboken::Conversions::getPythonTypeObject("QRemoteObjectNode");
+            if (!PyArg_UnpackTuple(args, "Replica.__init__", 2, 3, &node, &constructorType, &name) ||
+                !PySide::inherits(Py_TYPE(node), nodeType->tp_name)) {
+                PyErr_SetString(PyExc_TypeError,
+                                "Replicas can be initialized with no arguments or by node.acquire only");
+                return -1;
+            }
+            static auto *constructorArgs = PyTuple_Pack(1, constructorType);
+            if (initFunc(self, constructorArgs, kwds) < 0)
+                return -1;
+            if (name)
+                PyObject_CallMethod(self, "initializeNode", "OO", node, name);
+            else
+                PyObject_CallMethod(self, "initializeNode", "O", node);
+            Shiboken::Conversions::pythonToCppPointer(getSbkType(), self, &replica);
+        }
+        if (!replica) {
+            PyErr_SetString(PyExc_RuntimeError, "Failed to initialize replica");
+            return -1;
+        }
+        // Get the properties from the type
+        PyTypeObject *type = Py_TYPE(self);
+        auto *pyProperties = PyObject_GetAttr(reinterpret_cast<PyObject *>(type), propertiesAttr());
+        if (!pyProperties) {
+            PyErr_SetString(PyExc_RuntimeError, "Failed to get properties from type");
+            return -1;
+        }
+        // Make a copy of the properties and set them on the replica
+        auto *propPtr = reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(pyProperties, nullptr));
+        auto propertiesCopy = QVariantList(*propPtr);
+        static_cast<FriendlyReplica *>(replica)->setProperties(std::move(propertiesCopy));
+        return 0;
+    }
+
+    static PyObject *capsule_method_handler(PyObject *payload, PyObject *args)
+    {
+        auto *methodData = reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(payload,
+                                                                                          "Payload"));
+        if (!methodData) {
+            PyErr_SetString(PyExc_RuntimeError, "Invalid call to dynamic method.  Missing payload.");
+            return nullptr;
+        }
+        PyObject *self = methodData->self;
+        QRemoteObjectReplica *replica = nullptr;
+        Shiboken::Conversions::pythonToCppPointer(getSbkType(), self, &replica);
+        if (PyCapsule_IsValid(methodData->payload, "PropertyCapsule")) {
+            auto *capsule = PyCapsule_GetPointer(methodData->payload, "PropertyCapsule");
+            if (capsule) {
+                auto *callData = reinterpret_cast<PropertyCapsule *>(capsule);
+                QVariant currentVariant = static_cast<FriendlyReplica *>(replica)->propAsVariant(callData->indexInObject);
+
+                // Handle getter
+                if (PyTuple_Size(args) == 0) // Getter
+                    return toPython(currentVariant);
+
+                // Handle setter - currentVariant is a copy, so we can modify it
+                if (PyTuple_Size(args) != 1) {
+                    PyErr_SetString(PyExc_TypeError,
+                                    "Property setter takes exactly one argument");
+                    return nullptr;
+                }
+                Conversions::SpecificConverter converter(currentVariant.metaType().name());
+                auto metaType = currentVariant.metaType();
+                if (metaType.flags().testFlag(QMetaType::IsEnumeration)) {
+                    converter.toCpp(PyTuple_GetItem(args, 0), currentVariant.data());
+                    currentVariant.convert(metaType);
+                } else {
+                    converter.toCpp(PyTuple_GetItem(args, 0), currentVariant.data());
+                }
+                if (PyErr_Occurred()) // POD conversion can produce an error
+                    return nullptr;
+                QVariantList _args{currentVariant};
+                static_cast<FriendlyReplica *>(replica)->send(QMetaObject::WriteProperty, callData->propertyIndex, _args);
+                Py_RETURN_NONE;
+            }
+        }
+        if (PyCapsule_IsValid(methodData->payload, "MethodCapsule")) {
+            auto *capsule = PyCapsule_GetPointer(methodData->payload, "MethodCapsule");
+            if (capsule) {
+                auto *callData = reinterpret_cast<MethodCapsule *>(capsule);
+                if (PyTuple_Size(args) != callData->argumentTypes.size()) {
+                    PyErr_SetString(PyExc_TypeError,
+                                    "Method called with incorrect number of arguments");
+                    return nullptr;
+                }
+                QVariantList _args;
+                static Conversions::SpecificConverter argsConverter("QVariantList");
+                argsConverter.toCpp(args, &_args);
+                if (PyErr_Occurred()) // POD conversion can produce an error
+                    return nullptr;
+                if (!callData->returnType.isValid() ||
+                    (callData->returnType.isValid() && callData->returnType.id() == QMetaType::Void)) {
+                    static_cast<FriendlyReplica *>(replica)->send(QMetaObject::InvokeMetaMethod, callData->methodIndex, _args);
+                    Py_RETURN_NONE;
+                }
+                QRemoteObjectPendingCall *cppResult = new QRemoteObjectPendingCall;
+                *cppResult = static_cast<FriendlyReplica *>(replica)->sendWithReply(QMetaObject::InvokeMetaMethod,
+                                                    callData->methodIndex, _args);
+                static PyTypeObject *baseType =
+                    Shiboken::Conversions::getPythonTypeObject("QRemoteObjectPendingCall");
+                Q_ASSERT(baseType);
+                auto *pyResult = Shiboken::Object::newObject(baseType, cppResult, true, true);
+                return pyResult;
+            }
+        }
+
+        PyErr_SetString(PyExc_RuntimeError, "Unknown capsule type");
+        return nullptr;
+    }
+};
+
+static int DynamicType_traverse(PyObject *self, visitproc visit, void *arg)
+{
+    auto traverseProc = reinterpret_cast<traverseproc>(PepType_GetSlot(SbkObject_TypeF(),
+                                                                       Py_tp_traverse));
+    return traverseProc(self, visit, arg);
+}
+
+static int DynamicType_clear(PyObject *self)
+{
+    auto clearProc = reinterpret_cast<inquiry>(PepType_GetSlot(SbkObject_TypeF(), Py_tp_clear));
+    return clearProc(self);
+}
+
+static PyMethodDef DynamicClass_methods[] = {
+    {"get_enum", reinterpret_cast<PyCFunction>(DynamicType_get_enum), METH_O | METH_CLASS,
+     "Get enum type by name"},
+    {nullptr, nullptr, 0, nullptr}
+};
+
+static PyType_Slot DynamicClass_slots[] = {
+    {Py_tp_base,        nullptr}, // inserted by introduceWrapperType
+    {Py_tp_init,        nullptr}, // inserted by createDynamicType
+    {Py_tp_traverse,    reinterpret_cast<void *>(DynamicType_traverse)},
+    {Py_tp_clear,       reinterpret_cast<void *>(DynamicType_clear)},
+    {Py_tp_methods,     reinterpret_cast<void *>(DynamicClass_methods)},
+    {0, nullptr}
+};
+
+} // extern "C"
+
+template <typename T, typename BaseType>
+PyTypeObject *createDynamicClassImpl(QMetaObject *meta)
+{
+    DynamicClass_slots[1].pfunc = reinterpret_cast<void*>(T::tp_init);
+
+    auto fullTypeName = QByteArray{T::getTypePrefix()} + meta->className();
+    PyType_Spec spec = {
+        fullTypeName.constData(),
+        0,
+        0,
+        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
+        DynamicClass_slots
+    };
+
+    auto type = Shiboken::ObjectType::introduceWrapperType(
+        reinterpret_cast<PyObject *>(PySideRepFile_TypeF()),
+        meta->className(),
+        meta->className(),
+        &spec,
+        &Shiboken::callCppDestructor<BaseType>,
+        T::getBases(),
+        Shiboken::ObjectType::WrapperFlags::InternalWrapper);
+
+    auto *self = reinterpret_cast<PyObject *>(type);
+    if (create_managed_py_enums(self, meta) < 0)
+        return nullptr;
+
+    PySide::Signal::registerSignals(type, meta);
+    Shiboken::ObjectType::setSubTypeInitHook(type, &PySide::initQObjectSubType);
+    PySide::initDynamicMetaObject(type, meta, 0);  // Size 0?
+
+    PyMethodDef method = {
+        nullptr,
+        reinterpret_cast<PyCFunction>(T::capsule_method_handler),
+        METH_VARARGS,
+        nullptr
+    };
+    for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
+        // Create a PropertyCapsule for each property to store the info needed for
+        // the handler. Assign the __get__ and (if needed) __set__ attributes to a
+        // PySideProperty which becomes the attribute set on the new type.
+        auto metaProperty = meta->property(i);
+        PyObject *kwds = PyDict_New();
+        auto metaType = metaProperty.metaType();
+        auto *pyPropertyType = PyUnicode_FromString(metaType.name());
+        PyDict_SetItemString(kwds, "type", pyPropertyType);
+        Py_DECREF(pyPropertyType);
+
+        method.ml_name = metaProperty.name();
+        auto *pc = new PropertyCapsule{metaProperty.name(), i, i - meta->propertyOffset()};
+        auto capsule = PyCapsule_New(pc, "PropertyCapsule", [](PyObject *capsule) {
+            delete static_cast<PropertyCapsule *>(PyCapsule_GetPointer(capsule, "PropertyCapsule"));
+        });
+        auto capsulePropObject = make_capsule_property(&method, capsule,
+                                                       metaProperty.isWritable());
+        PyObject *fget = PyObject_GetAttrString(capsulePropObject, "__get__");
+        PyDict_SetItemString(kwds, "fget", fget);
+        if (metaProperty.isWritable()) {
+            PyObject *fset = PyObject_GetAttrString(capsulePropObject, "__set__");
+            PyDict_SetItemString(kwds, "fset", fset);
+            if (metaProperty.hasNotifySignal()) {
+                auto nameString = metaProperty.notifySignal().name();
+                auto *notify = PyObject_GetAttrString(reinterpret_cast<PyObject *>(type),
+                                                      nameString.constData());
+                PyDict_SetItemString(kwds, "notify", notify);
+            }
+        }
+        PyObject *pyProperty = PyObject_Call(reinterpret_cast<PyObject *>(PySideProperty_TypeF()),
+                                             PyTuple_New(0), kwds);
+        if (PyObject_SetAttrString(reinterpret_cast<PyObject *>(type),
+                                   metaProperty.name(), pyProperty) < 0) {
+            return nullptr;
+        }
+        Py_DECREF(pyProperty);
+    }
+    for (int i = meta->methodOffset(); i < meta->methodCount(); ++i) {
+        // Create a CapsuleMethod for each Slot method to store the info needed
+        // for the handler.
+        auto metaMethod = meta->method(i);
+        // Note: We are creating our custom metatype ourselves, which makes our added
+        // (non-signal), methods return QMetaMethod::MethodType::Method, not
+        // MethodType::Slot. This is fine, we just need to create a CapsuleMethod
+        // for those methods.
+        if (metaMethod.methodType() == QMetaMethod::MethodType::Signal)
+            continue;
+        auto name = metaMethod.name();
+        method.ml_name = name.constData();
+        QList<QMetaType> argumentTypes;
+        for (int j = 0; j < metaMethod.parameterCount(); ++j)
+            argumentTypes << metaMethod.parameterMetaType(j);
+        MethodCapsule *capsuleData = new MethodCapsule{metaMethod.name(),
+                                                       metaMethod.methodIndex(),
+                                                       std::move(argumentTypes),
+                                                       metaMethod.returnMetaType()};
+        add_capsule_method_to_type(type, &method,
+                                   PyCapsule_New(capsuleData, "MethodCapsule",
+                                                 [](PyObject *capsule) {
+            delete reinterpret_cast<MethodCapsule *>(PyCapsule_GetPointer(capsule, "MethodCapsule"));
+        }));
+    }
+
+    return type;
+}
+
+PyTypeObject *createDynamicClass(QMetaObject *meta, PyObject *properties_capsule)
+{
+    bool isSource;
+    if (strncmp(meta->superClass()->className(), "QObject", 7) == 0) {
+        isSource = true;
+    } else if (strncmp(meta->superClass()->className(), "QRemoteObjectReplica", 20) == 0) {
+        isSource = false;
+    } else {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "Dynamic type must be a subclass of QObject or QRemoteObjectReplica");
+        return nullptr;
+    }
+
+    PyTypeObject *newType = nullptr;
+
+    if (isSource)
+        newType = createDynamicClassImpl<SourceDefs, QObject>(meta);
+    else
+        newType = createDynamicClassImpl<ReplicaDefs, QRemoteObjectReplica>(meta);
+
+    // Add the properties to the new type as an attribute
+    if (PyObject_SetAttr(reinterpret_cast<PyObject *>(newType), propertiesAttr(),
+                         properties_capsule) < 0) {
+        Py_DECREF(newType);
+        return nullptr;
+    }
+
+    return newType;
+}
diff --git a/sources/pyside6/libpysideremoteobjects/pysidedynamicclass_p.h b/sources/pyside6/libpysideremoteobjects/pysidedynamicclass_p.h
new file mode 100644 (file)
index 0000000..4716f4f
--- /dev/null
@@ -0,0 +1,15 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_DYNAMIC_CLASS_P_H
+#define PYSIDE_DYNAMIC_CLASS_P_H
+
+#include <sbkpython.h>
+
+#include <QtCore/qtclasshelpermacros.h>
+
+QT_FORWARD_DECLARE_STRUCT(QMetaObject)
+
+PyTypeObject *createDynamicClass(QMetaObject *meta, PyObject *properties_capsule);
+
+#endif // PYSIDE_DYNAMIC_CLASS_P_H
diff --git a/sources/pyside6/libpysideremoteobjects/pysidedynamiccommon.cpp b/sources/pyside6/libpysideremoteobjects/pysidedynamiccommon.cpp
new file mode 100644 (file)
index 0000000..1e8bc32
--- /dev/null
@@ -0,0 +1,126 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysidedynamiccommon_p.h"
+#include "pysidedynamicenum_p.h"
+
+#include <sbkstring.h>
+
+#include <QtCore/qmetaobject.h>
+
+int capsule_count = 0;
+
+using namespace Shiboken;
+
+PyObject *toPython(const QVariant &variant)
+{
+    auto metaType = variant.metaType();
+    Conversions::SpecificConverter converter(metaType.name());
+    auto *value = converter.toPython(variant.data());
+    if (metaType.flags().testFlag(QMetaType::IsGadget)) {
+        // A single converter is used for all POD types - it converts to a Python
+        // tuple. We need an additional step to convert to our Python type for the POD.
+        // Thankfully, the converter stores the specific type we created, so we can call
+        // the constructor with the tuple.
+        auto *podType = Conversions::getPythonTypeObject(converter);
+        if (!podType) {
+            Py_DECREF(value);
+            PyErr_SetString(PyExc_RuntimeError, "Failed to get Python type for POD");
+            return nullptr;
+        }
+        PyObject *podValue = PyObject_CallObject(reinterpret_cast<PyObject *>(podType), value);
+        Py_DECREF(value);
+        if (!podValue) {
+            PyErr_SetString(PyExc_RuntimeError, "Failed to create POD instance");
+            return nullptr;
+        }
+        return podValue;
+    }
+    if (metaType.flags().testFlag(QMetaType::IsEnumeration)) {
+        // Enums are converted to Python ints
+        auto *enumType = Conversions::getPythonTypeObject(converter);
+        if (!enumType) {
+            Py_DECREF(value);
+            PyErr_SetString(PyExc_RuntimeError, "Failed to get Python type for enum");
+            return nullptr;
+        }
+        PyObject *enumValue = PyObject_CallFunctionObjArgs(reinterpret_cast<PyObject *>(enumType),
+                                                           value, nullptr);
+        Py_DECREF(value);
+        if (!enumValue) {
+            PyErr_Print();
+            PyErr_SetString(PyExc_RuntimeError, "Failed to create enum instance");
+            return nullptr;
+        }
+        return enumValue;
+    }
+    return value;
+}
+
+
+/**
+ * @brief Creates and manages memory for Python enum types for each QEnum in the
+ * provided QMetaObject.
+ *
+ * This function iterates over the enumerators in the provided QMetaObject,
+ * creates corresponding Python enum types, and stores them in a dictionary.
+ * The dictionary is then set as an attribute ()"_enum_data") on the provided
+ * Python object, to be accessed by the _get_enum that has been added to each
+ * of our dynamic types.
+ *
+ * These are "managed" in the sense that the enums clean up their converters
+ * using our PyCapsule method, and by adding the dictionary as a Python attribute,
+ * the dictionary will be cleaned up when the containing type is garbage
+ * collected.
+ *
+ * @param self A pointer to the Python object where the enum data will be stored.
+ * @param meta A pointer to the QMetaObject containing the enumerators.
+ * @return Returns 0 on success, or -1 on failure.
+ */
+int create_managed_py_enums(PyObject *self, QMetaObject *meta)
+{
+    PyObject *enum_data = PyDict_New();
+    for (int i = meta->enumeratorOffset(); i < meta->enumeratorCount(); ++i) {
+        auto metaEnum = meta->enumerator(i);
+        auto *enumType = createEnumType(&metaEnum);
+        if (!enumType) {
+            PyErr_Print();
+            PyErr_Format(PyExc_RuntimeError, "Failed to create enum type for POD '%s'",
+                         meta->className());
+            return -1;
+        }
+        PyDict_SetItemString(enum_data, metaEnum.enumName(),
+                             reinterpret_cast<PyObject *>(enumType));
+        Py_DECREF(enumType);
+    }
+    if (PyObject_SetAttrString(self, "_enum_data", enum_data) < 0) {
+        PyErr_Print();
+        qWarning() << "Failed to set _enum_data attribute on type"
+                   << reinterpret_cast<PyTypeObject *>(self)->tp_name;
+        return -1;
+    }
+    Py_DECREF(enum_data);
+
+    return 0;
+}
+
+PyObject *DynamicType_get_enum(PyObject *self, PyObject *name)
+{
+    // Our enum types are always stored in a dictionary attribute named "_enum_data"
+    PyObject *enum_dict = PyObject_GetAttrString(self, "_enum_data");
+    if (!enum_dict) {
+        PyErr_SetString(PyExc_RuntimeError, "Failed to get _enum_data attribute");
+        return nullptr;
+    }
+
+    PyObject *enum_type = PyDict_GetItem(enum_dict, name);
+    Py_DECREF(enum_dict);
+
+    if (!enum_type) {
+        PyErr_Format(PyExc_KeyError, "Enum '%s' not found", String::toCString(name));
+        return nullptr;
+    }
+
+    Py_INCREF(enum_type);
+    return enum_type;
+}
diff --git a/sources/pyside6/libpysideremoteobjects/pysidedynamiccommon_p.h b/sources/pyside6/libpysideremoteobjects/pysidedynamiccommon_p.h
new file mode 100644 (file)
index 0000000..041e8bd
--- /dev/null
@@ -0,0 +1,84 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_DYNAMIC_COMMON_P_H
+#define PYSIDE_DYNAMIC_COMMON_P_H
+
+#include <sbkconverter.h>
+
+#include <QtCore/qlist.h>
+#include <QtCore/qvariant.h>
+#include <QtCore/qmetatype.h>
+
+PyObject *toPython(const QVariant &variant);
+int create_managed_py_enums(PyObject *self, QMetaObject *meta);
+PyObject *DynamicType_get_enum(PyObject *self, PyObject *name);
+
+// Data for dynamically created property handlers
+struct PropertyCapsule
+{
+    QByteArray name;
+    int propertyIndex;   // meta->indexOfProperty() - including offset
+    int indexInObject;   // Index minus offset for indexing into QVariantList
+};
+
+// Data for dynamically created method handlers
+struct MethodCapsule
+{
+    QByteArray name;
+    int methodIndex;
+    QList<QMetaType> argumentTypes;
+    QMetaType returnType; // meta->indexOfMethod() - including offset
+};
+
+// These functions are used to create a PyCapsule holding a pointer to a C++
+// object, which is set as an attribute on a Python type. When the Python
+// type is garbage collected, the type's attributes are as well, resulting in
+// the capsule's cleanup running to delete the pointer. This won't be as
+// efficient as a custom tp_free on the type, but it's easier to manage.
+// And it only runs when as all references to the type (and all instances) are
+// released, so it won't be used frequently.
+
+extern int capsule_count;
+
+template <typename T>
+void Capsule_destructor(PyObject *capsule)
+{
+    capsule_count--;
+    T pointer = static_cast<T>(PyCapsule_GetPointer(capsule, nullptr));
+    delete pointer;
+    pointer = nullptr;
+}
+
+template <>
+inline void Capsule_destructor<SbkConverter *>(PyObject *capsule)
+{
+    capsule_count--;
+    SbkConverter *pointer = static_cast<SbkConverter *>(PyCapsule_GetPointer(capsule, nullptr));
+    Shiboken::Conversions::deleteConverter(pointer);
+    pointer = nullptr;
+}
+
+template <typename T>
+int set_cleanup_capsule_attr_for_pointer(PyTypeObject *type, const char *name, T pointer)
+{
+    static_assert(std::is_pointer<T>::value, "T must be a pointer type");
+
+    if (!pointer) {
+        PyErr_SetString(PyExc_RuntimeError, "Pointer is null");
+        return -1;
+    }
+    auto capsule = PyCapsule_New(pointer, nullptr, Capsule_destructor<T>);
+    if (!capsule)
+        return -1;  // Propagate the error
+
+    if (PyObject_SetAttrString(reinterpret_cast<PyObject *>(type), name, capsule) < 0)
+        return -1;  // Propagate the error
+
+    Py_DECREF(capsule);
+    capsule_count++;
+
+    return 0;
+}
+
+#endif // PYSIDE_DYNAMIC_COMMON_P_H
diff --git a/sources/pyside6/libpysideremoteobjects/pysidedynamicenum.cpp b/sources/pyside6/libpysideremoteobjects/pysidedynamicenum.cpp
new file mode 100644 (file)
index 0000000..1f92224
--- /dev/null
@@ -0,0 +1,158 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysidedynamicenum_p.h"
+#include "pysidedynamiccommon_p.h"
+
+#include <autodecref.h>
+#include <sbkconverter.h>
+#include <sbkenum.h>
+
+#include <QtCore/qmetaobject.h>
+
+using namespace Shiboken;
+
+// Remote Objects transfer enums as integers of the underlying type.
+#define CREATE_ENUM_CONVERSION_FUNCTIONS(SUFFIX, INT_TYPE, PY_TYPE) \
+static void pythonToCpp_PyEnum_QEnum_##SUFFIX(PyObject *pyIn, void *cppOut) \
+{ \
+    Enum::EnumValueType value = Enum::getValue(pyIn); \
+    INT_TYPE val(value); \
+    *reinterpret_cast<INT_TYPE *>(cppOut) = val; \
+} \
+static PythonToCppFunc is_PyEnum_PythonToCpp_QEnum_##SUFFIX##_Convertible(PyObject *pyIn) \
+{ \
+    if (Enum::check(pyIn)) \
+        return pythonToCpp_PyEnum_QEnum_##SUFFIX; \
+    return {}; \
+} \
+static PyObject *cppToPython_QEnum_##SUFFIX##_PyEnum(const void *cppIn) \
+{ \
+    auto convertedCppIn = *reinterpret_cast<const INT_TYPE *>(cppIn); \
+    return PY_TYPE(convertedCppIn); \
+}
+
+CREATE_ENUM_CONVERSION_FUNCTIONS(I8, int8_t, PyLong_FromLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(I16, int16_t, PyLong_FromLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(I32, int32_t, PyLong_FromLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(U8, uint8_t, PyLong_FromUnsignedLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(U16, uint16_t, PyLong_FromUnsignedLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(U32, uint32_t, PyLong_FromUnsignedLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(I64, int64_t, PyLong_FromLongLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(U64, uint64_t, PyLong_FromUnsignedLongLong)
+
+PyTypeObject *createEnumType(QMetaEnum *metaEnum)
+{
+    static const auto namePrefix = QByteArrayLiteral("2:PySide6.QtRemoteObjects.DynamicEnum.");
+    auto fullName = namePrefix + metaEnum->scope() + "." + metaEnum->enumName();
+
+    AutoDecRef args(PyList_New(0));
+    auto *pyEnumItems = args.object();
+    auto metaType = metaEnum->metaType();
+    auto underlyingType = metaType.underlyingType();
+    bool isUnsigned = underlyingType.flags().testFlag(QMetaType::IsUnsignedEnumeration);
+    for (int idx = 0; idx < metaEnum->keyCount(); ++idx) {
+        auto *key = PyUnicode_FromString(metaEnum->key(idx));
+        auto *key_value = PyTuple_New(2);
+        PyTuple_SetItem(key_value, 0, key);
+        // Value should only return a nullopt if there is no metaObject or the index is not valid
+        auto valueOpt = metaEnum->value64(idx);
+        if (!valueOpt) {
+            PyErr_SetString(PyExc_RuntimeError, "Failed to get value64 from enum");
+            return nullptr;
+        }
+        if (isUnsigned) {
+            auto *value = PyLong_FromUnsignedLongLong(*valueOpt);
+            PyTuple_SetItem(key_value, 1, value);
+        } else {
+            auto *value = PyLong_FromLongLong(*valueOpt);
+            PyTuple_SetItem(key_value, 1, value);
+        }
+        PyList_Append(pyEnumItems, key_value);
+    }
+
+    PyTypeObject *newType{};
+    if (metaEnum->isFlag())
+        newType = Enum::createPythonEnum(fullName.constData(), pyEnumItems, "Flag");
+    else
+        newType = Enum::createPythonEnum(fullName.constData(), pyEnumItems);
+
+    SbkConverter *converter = nullptr;
+    switch (underlyingType.sizeOf()) {
+        case 1:
+            if (isUnsigned) {
+                converter = Conversions::createConverter(newType,
+                                                         cppToPython_QEnum_U8_PyEnum);
+                Conversions::addPythonToCppValueConversion(converter,
+                                                           pythonToCpp_PyEnum_QEnum_U8,
+                                                           is_PyEnum_PythonToCpp_QEnum_U8_Convertible);
+            } else {
+                converter = Conversions::createConverter(newType,
+                                                         cppToPython_QEnum_I8_PyEnum);
+                Conversions::addPythonToCppValueConversion(converter,
+                                                           pythonToCpp_PyEnum_QEnum_I8,
+                                                           is_PyEnum_PythonToCpp_QEnum_I8_Convertible);
+            }
+            break;
+        case 2:
+            if (isUnsigned) {
+                converter = Conversions::createConverter(newType,
+                                                         cppToPython_QEnum_U16_PyEnum);
+                Conversions::addPythonToCppValueConversion(converter,
+                                                           pythonToCpp_PyEnum_QEnum_U16,
+                                                           is_PyEnum_PythonToCpp_QEnum_U16_Convertible);
+            } else {
+                converter = Conversions::createConverter(newType,
+                                                         cppToPython_QEnum_I16_PyEnum);
+                Conversions::addPythonToCppValueConversion(converter,
+                                                           pythonToCpp_PyEnum_QEnum_I16,
+                                                           is_PyEnum_PythonToCpp_QEnum_I16_Convertible);
+            }
+            break;
+        case 4:
+            if (isUnsigned) {
+                converter = Conversions::createConverter(newType,
+                                                         cppToPython_QEnum_U32_PyEnum);
+                Conversions::addPythonToCppValueConversion(converter,
+                                                           pythonToCpp_PyEnum_QEnum_U32,
+                                                           is_PyEnum_PythonToCpp_QEnum_U32_Convertible);
+            } else {
+                converter = Conversions::createConverter(newType,
+                                                         cppToPython_QEnum_I32_PyEnum);
+                Conversions::addPythonToCppValueConversion(converter,
+                                                           pythonToCpp_PyEnum_QEnum_I32,
+                                                           is_PyEnum_PythonToCpp_QEnum_I32_Convertible);
+            }
+            break;
+        case 8:
+            if (isUnsigned) {
+                converter = Conversions::createConverter(newType,
+                                                         cppToPython_QEnum_U64_PyEnum);
+                Conversions::addPythonToCppValueConversion(converter,
+                                                           pythonToCpp_PyEnum_QEnum_U64,
+                                                           is_PyEnum_PythonToCpp_QEnum_U64_Convertible);
+            } else {
+                converter = Conversions::createConverter(newType,
+                                                         cppToPython_QEnum_I64_PyEnum);
+                Conversions::addPythonToCppValueConversion(converter,
+                                                           pythonToCpp_PyEnum_QEnum_I64,
+                                                           is_PyEnum_PythonToCpp_QEnum_I64_Convertible);
+            }
+            break;
+        default:
+            PyErr_SetString(PyExc_RuntimeError, "Unsupported enum underlying type");
+            return nullptr;
+    }
+    auto scopedName = QByteArray(metaEnum->scope()) + "::" + metaEnum->enumName();
+    Conversions::registerConverterName(converter, scopedName.constData());
+    Conversions::registerConverterName(converter, metaEnum->enumName());
+    // createConverter increases the ref count of type, but that will create a
+    // circular reference when we add the capsule with the converter's pointer
+    // to the type's attributes. So we need to decrease the ref count on the
+    // type after calling createConverter.
+    Py_DECREF(newType);
+    if (set_cleanup_capsule_attr_for_pointer(newType, "_converter_capsule", converter) < 0)
+        return nullptr;
+
+    return newType;
+}
diff --git a/sources/pyside6/libpysideremoteobjects/pysidedynamicenum_p.h b/sources/pyside6/libpysideremoteobjects/pysidedynamicenum_p.h
new file mode 100644 (file)
index 0000000..14181fa
--- /dev/null
@@ -0,0 +1,15 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_DYNAMIC_ENUM_P_H
+#define PYSIDE_DYNAMIC_ENUM_P_H
+
+#include <sbkpython.h>
+
+#include <QtCore/qtclasshelpermacros.h>
+
+QT_FORWARD_DECLARE_CLASS(QMetaEnum)
+
+PyTypeObject *createEnumType(QMetaEnum *metaEnum);
+
+#endif // PYSIDE_DYNAMIC_ENUM_P_H
diff --git a/sources/pyside6/libpysideremoteobjects/pysidedynamicpod.cpp b/sources/pyside6/libpysideremoteobjects/pysidedynamicpod.cpp
new file mode 100644 (file)
index 0000000..abfeaa0
--- /dev/null
@@ -0,0 +1,260 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysidedynamicpod_p.h"
+#include "pysidecapsulemethod_p.h"
+#include "pysidedynamiccommon_p.h"
+
+#include <autodecref.h>
+#include <helper.h>
+#include <pep384ext.h>
+#include <sbkconverter.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
+
+#include <pysidestaticstrings.h>
+
+#include <QtCore/qmetaobject.h>
+
+using namespace Shiboken;
+
+extern "C"
+{
+
+struct PodDefs
+{
+    static PyObject *tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+    {
+        SBK_UNUSED(kwds);
+        AutoDecRef param_types(PyObject_GetAttrString(reinterpret_cast<PyObject *>(type),
+                                                      "__param_types__"));
+        if (!param_types) {
+            PyErr_Format(PyExc_RuntimeError, "Failed to get POD attributes for type %s",
+                         type->tp_name);
+            return nullptr;
+        }
+
+        // param_types is a tuple of PyTypeObject pointers
+        Py_ssize_t size = PyTuple_Size(param_types);
+        if (size != PyTuple_Size(args)) {
+            PyErr_Format(PyExc_TypeError,
+                         "Incorrect number of arguments for type %s.  Expected %zd.",
+                         type->tp_name, size);
+            return nullptr;
+        }
+
+        PyObject *self = PepExt_Type_GetAllocSlot(type)(type, size);
+
+        if (!self)
+            return nullptr;
+
+        for (Py_ssize_t i = 0; i < size; ++i) {
+            PyObject *expected_type = PyTuple_GetItem(param_types, i);
+            PyObject *item = PyTuple_GetItem(args, i);
+            // Check if the item is an instance of the expected type
+            if (PyObject_IsInstance(item, expected_type)) {
+                Py_INCREF(item);
+                PyTuple_SetItem(self, i, item);
+            } else {
+                // Try to convert the item to the expected type
+                PyObject *converted_item = PyObject_CallFunctionObjArgs(expected_type, item, nullptr);
+                if (!converted_item) {
+                    Py_DECREF(self);
+                    PyErr_Format(PyExc_TypeError, "Argument %zd must be convertible to type %s", i,
+                                 reinterpret_cast<PyTypeObject *>(expected_type)->tp_name);
+                    return nullptr;
+                }
+                PyTuple_SetItem(self, i, converted_item);
+            }
+        }
+
+        return self;
+    }
+
+    static PyObject *tp_repr(PyObject *self)
+    {
+        auto *type = Py_TYPE(self);
+        std::string repr(type->tp_name);
+        repr += "(";
+        for (Py_ssize_t i = 0; i < PyTuple_Size(self); ++i) {
+            if (i > 0)
+                repr += ", ";
+
+            PyObject *item_repr = PyObject_Repr(PyTuple_GetItem(self, i));
+            repr += String::toCString(item_repr);
+        }
+        repr += ")";
+        return PyUnicode_FromString(repr.c_str());
+    }
+
+    static PyObject *CapsuleMethod_handler(PyObject *payload, PyObject * /* args */)
+    {
+        auto *methodData = reinterpret_cast<CapsuleDescriptorData *>(
+            PyCapsule_GetPointer(payload, "Payload"));
+        if (!methodData) {
+            PyErr_SetString(PyExc_RuntimeError, "Invalid call to dynamic method.  Missing payload.");
+            return nullptr;
+        }
+        PyObject *self = methodData->self;
+        if (PyCapsule_IsValid(methodData->payload, "PropertyCapsule")) {
+            // Handle property getter/setter against our hidden properties attribute
+            auto *capsule = PyCapsule_GetPointer(methodData->payload, "PropertyCapsule");
+            if (capsule) {
+                auto *callData = reinterpret_cast<PropertyCapsule *>(capsule);
+                if (callData->indexInObject < 0 || callData->indexInObject >= PyTuple_Size(self)) {
+                    PyErr_Format(PyExc_RuntimeError, "Unknown property method: %s",
+                                 callData->name.constData());
+                    return nullptr;
+                }
+                auto *val = PyTuple_GetItem(self, callData->indexInObject);
+                Py_INCREF(val);
+                return val;
+            }
+        }
+
+        PyErr_SetString(PyExc_RuntimeError, "Unknown capsule type");
+        return nullptr;
+    }
+};
+
+static PyMethodDef DynamicPod_tp_methods[] = {
+    {"get_enum", reinterpret_cast<PyCFunction>(DynamicType_get_enum), METH_O | METH_CLASS,
+                 "Get enum type by name"},
+    {nullptr, nullptr, 0, nullptr}
+};
+
+static PyType_Slot DynamicPod_slots[] = {
+    {Py_tp_base,        reinterpret_cast<void *>(&PyTuple_Type)},
+    {Py_tp_new,         reinterpret_cast<void *>(PodDefs::tp_new)},
+    {Py_tp_repr,        reinterpret_cast<void *>(PodDefs::tp_repr)},
+    {Py_tp_methods,     reinterpret_cast<void *>(DynamicPod_tp_methods)},
+    {0, nullptr}
+};
+
+// C++ to Python conversion for POD types.
+static PyObject *cppToPython_POD_Tuple(const void *cppIn)
+{
+    const auto &cppInRef = *reinterpret_cast<const QVariantList *>(cppIn);
+    PyObject *pyOut = PyTuple_New(Py_ssize_t(cppInRef.size()));
+    Py_ssize_t idx = 0;
+    for (auto it = std::cbegin(cppInRef), end = std::cend(cppInRef); it != end; ++it, ++idx) {
+        static const Conversions::SpecificConverter argConverter("QVariant");
+        const auto &cppItem = *it;
+        PyTuple_SetItem(pyOut, idx, Shiboken::Conversions::copyToPython(argConverter, &cppItem));
+    }
+    return pyOut;
+}
+static void pythonToCpp_Tuple_POD(PyObject *pyIn, void *cppOut)
+{
+    auto &cppOutRef = *reinterpret_cast<QVariantList *>(cppOut);
+
+    Py_ssize_t tupleSize = PyTuple_Size(pyIn);
+    if (tupleSize != cppOutRef.size()) {
+        PyErr_Format(PyExc_ValueError,
+                     "Size mismatch: tuple has %zd elements, but POD expects %d elements",
+                     tupleSize, cppOutRef.size());
+        return;
+    }
+
+    for (Py_ssize_t i = 0; i < tupleSize; ++i) {
+        static const Conversions::SpecificConverter argConverter("QVariant");
+        PyObject *item = PyTuple_GetItem(pyIn, i);
+        QVariant &variant = cppOutRef[i];
+        Conversions::SpecificConverter converter(variant.metaType().name());
+        Shiboken::Conversions::pythonToCppCopy(converter, item, variant.data());
+    }
+}
+static PythonToCppFunc is_Tuple_PythonToCpp_POD_Convertible(PyObject *pyIn)
+{
+    if (PyTuple_Check(pyIn))
+        return pythonToCpp_Tuple_POD;
+
+    return {};
+}
+
+} // extern "C"
+
+PyTypeObject *createPodType(QMetaObject *meta)
+{
+    auto qualname = QByteArrayLiteral("DynamicPod.") + meta->className();
+    PyType_Spec spec = {
+        qualname.constData(),
+        0,
+        0,
+        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_TYPE_SUBCLASS,
+        DynamicPod_slots
+    };
+
+    PyObject *obType = PyType_FromSpec(&spec);
+    if (!obType)
+        return nullptr;
+
+    if (create_managed_py_enums(obType, meta) < 0)
+        return nullptr;
+
+    Py_ssize_t size = meta->propertyCount() - meta->propertyOffset();
+    AutoDecRef pyParamTypes(PyTuple_New(size));
+    for (int i = 0; i < size; ++i) {
+        auto metaProperty = meta->property(i + meta->propertyOffset());
+        auto metaType = metaProperty.metaType();
+        if (!metaType.isValid()) {
+            PyErr_Format(PyExc_RuntimeError, "Failed to get meta type for property %s",
+                         metaProperty.name());
+            return nullptr;
+        }
+        auto *pyType = Conversions::getPythonTypeObject(metaType.name());
+        Py_INCREF(pyType);
+        PyTuple_SetItem(pyParamTypes, i, reinterpret_cast<PyObject *>(pyType));
+    }
+
+    auto *type = reinterpret_cast<PyTypeObject *>(obType);
+    PyMethodDef method = {
+        nullptr,
+        reinterpret_cast<PyCFunction>(PodDefs::CapsuleMethod_handler),
+        METH_VARARGS,
+        nullptr
+    };
+    for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
+        // Create a PropertyCapsule for each property to store the info needed
+        // for the handler.
+        auto metaProperty = meta->property(i);
+
+        method.ml_name = metaProperty.name();
+        auto *capsule = PyCapsule_New(new PropertyCapsule{metaProperty.name(),
+                                                          i,
+                                                          i - meta->propertyOffset()},
+                                      "PropertyCapsule",
+                                      [](PyObject *capsule) {
+                                          delete static_cast<PropertyCapsule *>(
+                                              PyCapsule_GetPointer(capsule, "PropertyCapsule"));
+                                      });
+        auto *capsulePropObject = make_capsule_property(&method, capsule);
+        if (PyObject_SetAttrString(reinterpret_cast<PyObject *>(type), metaProperty.name(),
+                                   capsulePropObject) < 0) {
+            return nullptr;
+        }
+
+        Py_DECREF(capsulePropObject);
+    }
+
+    // createConverter increases the ref count of type, but that will create
+    // a circular reference. When we add the capsule with the converter's pointer
+    // to the type's attributes. So we need to decrease the ref count on the type
+    // after calling createConverter.
+    auto *converter = Shiboken::Conversions::createConverter(type, cppToPython_POD_Tuple);
+    Py_DECREF(type);
+    if (set_cleanup_capsule_attr_for_pointer(type, "_converter_capsule", converter) < 0)
+        return nullptr;
+    Shiboken::Conversions::registerConverterName(converter, meta->className());
+    Shiboken::Conversions::registerConverterName(converter, type->tp_name);
+    Shiboken::Conversions::addPythonToCppValueConversion(converter, pythonToCpp_Tuple_POD,
+                                                         is_Tuple_PythonToCpp_POD_Convertible);
+
+    static PyObject *const module = String::createStaticString("PySide6.QtRemoteObjects");
+    AutoDecRef pyQualname(String::fromCString(qualname.constData()));
+    PyObject_SetAttr(obType, PyMagicName::qualname(), pyQualname);
+    PyObject_SetAttr(obType, PyMagicName::module(), module);
+    PyObject_SetAttrString(obType, "__param_types__", pyParamTypes);
+
+    return type;
+}
diff --git a/sources/pyside6/libpysideremoteobjects/pysidedynamicpod_p.h b/sources/pyside6/libpysideremoteobjects/pysidedynamicpod_p.h
new file mode 100644 (file)
index 0000000..6dc9db9
--- /dev/null
@@ -0,0 +1,15 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_DYNAMIC_POD_P_H
+#define PYSIDE_DYNAMIC_POD_P_H
+
+#include <sbkpython.h>
+
+#include <QtCore/qtclasshelpermacros.h>
+
+QT_FORWARD_DECLARE_STRUCT(QMetaObject)
+
+PyTypeObject *createPodType(QMetaObject *meta);
+
+#endif // PYSIDE_DYNAMIC_POD_P_H
diff --git a/sources/pyside6/libpysideremoteobjects/pysideremoteobjects.h b/sources/pyside6/libpysideremoteobjects/pysideremoteobjects.h
new file mode 100644 (file)
index 0000000..e0828c9
--- /dev/null
@@ -0,0 +1,16 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDEREMOTEOBJECTS_H
+#define PYSIDEREMOTEOBJECTS_H
+
+#include <sbkpython.h>
+
+namespace PySide::RemoteObjects
+{
+
+void init(PyObject *module);
+
+} // namespace PySide::RemoteObjects
+
+#endif // PYSIDEREMOTEOBJECTS_H
diff --git a/sources/pyside6/libpysideremoteobjects/pysiderephandler.cpp b/sources/pyside6/libpysideremoteobjects/pysiderephandler.cpp
new file mode 100644 (file)
index 0000000..bfe0854
--- /dev/null
@@ -0,0 +1,463 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysiderephandler_p.h"
+#include "pysidedynamicclass_p.h"
+#include "pysidedynamicpod_p.h"
+#include "pysidedynamiccommon_p.h"
+
+#include <pep384ext.h>
+#include <sbkstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
+
+#include <pysideutils.h>
+
+#include <QtCore/qbuffer.h>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qmetaobject.h>
+
+#include <QtRemoteObjects/qremoteobjectreplica.h>
+#include <QtRemoteObjects/qremoteobjectpendingcall.h>
+
+#include <private/qremoteobjectrepparser_p.h>
+
+using namespace Qt::StringLiterals;
+using namespace Shiboken;
+
+/**
+ * @file pysiderephandler.cpp
+ * @brief This file contains the implementation of the PySideRepFile type and its
+ * associated methods for handling Qt Remote Objects in PySide6.
+ *
+ * The PySideRepFile type provides functionality to parse and handle Qt Remote Objects
+ * (QtRO) files, and dynamically generate Python types for QtRO sources, replicas, and
+ * PODs (Plain Old Data structures).
+ *
+ * The RepFile_tp_methods array defines the methods available on the PySideRepFile object:
+ * - source: Generates a dynamic Python type for a QtRO source class.
+ * - replica: Generates a dynamic Python type for a QtRO replica class.
+ * - pod: Generates a dynamic Python type for a QtRO POD class.
+ *
+ * When generating a source or replica type, the generateDynamicType function is
+ * used, creating a new Python type based on the generated QMetaObject, and adds
+ * method descriptors for the required methods. A QVariantList for the types
+ * properties is also created, populated with default values if set in the input
+ * .rep file.
+*/
+
+static QVariantList generateProperties(QMetaObject *meta, const ASTClass &astClass);
+
+extern "C"
+{
+static PyObject *get_capsule_count()
+{
+    return PyLong_FromLong(capsule_count);
+}
+
+// Code for the PySideRepFile type
+static PyObject *RepFile_tp_string(PyObject *self);
+static PyObject *RepFile_tp_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds);
+static int RepFile_tp_init(PyObject *self, PyObject *args, PyObject *kwds);
+static void RepFile_tp_free(void *self);
+static void RepFile_tp_dealloc(PySideRepFile *self);
+
+static PyObject *RepFile_get_pods(PySideRepFile *self, void * /*unused*/);
+static PyObject *RepFile_get_replicas(PySideRepFile *self, void * /*unused*/);
+static PyObject *RepFile_get_sources(PySideRepFile *self, void * /*unused*/);
+
+bool instantiateFromDefaultValue(QVariant &variant, const QString &defaultValue);
+
+static PyObject *cppToPython_POD_Tuple(const void *cppIn);
+static void pythonToCpp_Tuple_POD(PyObject *pyIn, void *cppOut);
+static PythonToCppFunc is_Tuple_PythonToCpp_POD_Convertible(PyObject *pyIn);
+
+static PyGetSetDef RepFile_tp_getters[] = {
+    {"pod", reinterpret_cast<getter>(RepFile_get_pods), nullptr, "POD dictionary", nullptr},
+    {"replica", reinterpret_cast<getter>(RepFile_get_replicas), nullptr, "Replica dictionary", nullptr},
+    {"source", reinterpret_cast<getter>(RepFile_get_sources), nullptr, "Source dictionary", nullptr},
+    {nullptr, nullptr, nullptr, nullptr, nullptr}  // Sentinel
+};
+
+static PyTypeObject *createRepFileType()
+{
+    PyType_Slot PySideRepFileType_slots[] = {
+        {Py_tp_str,         reinterpret_cast<void *>(RepFile_tp_string)},
+        {Py_tp_init,        reinterpret_cast<void *>(RepFile_tp_init)},
+        {Py_tp_new,         reinterpret_cast<void *>(RepFile_tp_new)},
+        {Py_tp_free,        reinterpret_cast<void *>(RepFile_tp_free)},
+        {Py_tp_dealloc,     reinterpret_cast<void *>(RepFile_tp_dealloc)},
+        {Py_tp_getset,      reinterpret_cast<void *>(RepFile_tp_getters)},
+        {0, nullptr}
+    };
+
+    PyType_Spec PySideRepFileType_spec = {
+        "2:PySide6.QtRemoteObjects.RepFile",
+        sizeof(PySideRepFile),
+        0,
+        Py_TPFLAGS_DEFAULT,
+        PySideRepFileType_slots};
+    return SbkType_FromSpec(&PySideRepFileType_spec);
+}
+
+PyTypeObject *PySideRepFile_TypeF(void)
+{
+    static auto *type = createRepFileType();
+    return type;
+}
+
+static PyObject *RepFile_tp_string(PyObject *self)
+{
+    auto *cppSelf = reinterpret_cast<PySideRepFile *>(self);
+    QString result = QStringLiteral("RepFile(Classes: [%1], PODs: [%2])")
+                        .arg(cppSelf->d->classes.join(", "_L1), cppSelf->d->pods.join(", "_L1));
+    return PyUnicode_FromString(result.toUtf8().constData());
+}
+
+static PyObject *RepFile_tp_new(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */)
+{
+    auto *me = PepExt_TypeCallAlloc<PySideRepFile>(subtype, 0);
+    auto *priv = new PySideRepFilePrivate;
+    priv->podDict = PyDict_New();
+    if (!priv->podDict) {
+        delete priv;
+        return nullptr;
+    }
+    priv->replicaDict = PyDict_New();
+    if (!priv->replicaDict) {
+        Py_DECREF(priv->podDict);
+        delete priv;
+        return nullptr;
+    }
+    priv->sourceDict = PyDict_New();
+    if (!priv->sourceDict) {
+        Py_DECREF(priv->podDict);
+        Py_DECREF(priv->replicaDict);
+        delete priv;
+        return nullptr;
+    }
+    me->d = priv;
+    return reinterpret_cast<PyObject *>(me);
+}
+
+static PyObject *RepFile_get_pods(PySideRepFile *self, void * /* closure */)
+{
+    Py_INCREF(self->d->podDict);
+    return self->d->podDict;
+}
+
+static PyObject *RepFile_get_replicas(PySideRepFile *self, void * /* closure */)
+{
+    Py_INCREF(self->d->replicaDict);
+    return self->d->replicaDict;
+}
+
+static PyObject *RepFile_get_sources(PySideRepFile *self, void * /* closure */)
+{
+    Py_INCREF(self->d->sourceDict);
+    return self->d->sourceDict;
+}
+
+static void RepFile_tp_dealloc(PySideRepFile *self)
+{
+    Py_XDECREF(self->d->podDict);
+    Py_XDECREF(self->d->replicaDict);
+    Py_XDECREF(self->d->sourceDict);
+    PepExt_TypeCallFree(reinterpret_cast<PyObject *>(self));
+}
+
+static int parseArgsToAST(PyObject *args, PySideRepFile *repFile)
+{
+    // Verify args is a single string argument
+    if (PyTuple_Size(args) != 1 || !PyUnicode_Check(PyTuple_GetItem(args, 0))) {
+        PyErr_SetString(PyExc_TypeError, "RepFile constructor requires a single string argument");
+        return -1;
+    }
+
+    // Wrap contents into a QBuffer
+    const auto contents = PySide::pyStringToQString(PyTuple_GetItem(args, 0));
+    auto byteArray = contents.toUtf8();
+    QBuffer buffer(&byteArray);
+    buffer.open(QIODevice::ReadOnly);
+    RepParser repparser(buffer);
+    if (!repparser.parse()) {
+        PyErr_Format(PyExc_RuntimeError, "Error parsing input, line %d: error: %s",
+                     repparser.lineNumber(), qPrintable(repparser.errorString()));
+        auto lines = contents.split("\n"_L1);
+        auto lMin = std::max(1, repparser.lineNumber() - 2);
+        auto lMax = std::min(repparser.lineNumber() + 2, int(lines.size()));
+        // Print a few lines around the error
+        qWarning() << "Contents:";
+        for (int i = lMin; i <= lMax; ++i) {
+            if (i == repparser.lineNumber())
+                qWarning().nospace() << " line " << i << ": > " << lines.at(i - 1);
+            else
+                qWarning().nospace() << " line " << i << ":   " << lines.at(i - 1);
+        }
+        return -1;
+    }
+
+    repFile->d->ast = repparser.ast();
+
+    return 0;
+}
+
+static const char *repName(QMetaObject *meta)
+{
+    const int ind = meta->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE);
+    return ind >= 0 ? meta->classInfo(ind).value() : "<Invalid RemoteObject>";
+}
+
+static int RepFile_tp_init(PyObject *self, PyObject *args, PyObject * /* kwds */)
+{
+    auto *cppSelf = reinterpret_cast<PySideRepFile *>(self);
+    if (parseArgsToAST(args, cppSelf) < 0)
+        return -1;
+
+    for (const auto &pod : std::as_const(cppSelf->d->ast.pods)) {
+        cppSelf->d->pods << pod.name;
+        auto *qobject = new QObject;
+        auto *meta = createAndRegisterMetaTypeFromPOD(pod, qobject);
+        if (!meta) {
+            delete qobject;
+            PyErr_Format(PyExc_RuntimeError, "Failed to create meta object for POD '%s'",
+                         pod.name.toUtf8().constData());
+            return -1;
+        }
+
+        PyTypeObject *newType = createPodType(meta);
+        if (!newType) {
+            delete qobject;
+            PyErr_Print();
+            PyErr_Format(PyExc_RuntimeError, "Failed to create POD type for POD '%s'",
+                         pod.name.toUtf8().constData());
+            return -1;
+        }
+        if (set_cleanup_capsule_attr_for_pointer(newType, "_qobject_capsule", qobject) < 0) {
+            delete qobject;
+            return -1;
+        }
+
+        PyDict_SetItemString(cppSelf->d->podDict, meta->className(),
+                             reinterpret_cast<PyObject *>(newType));
+        Py_DECREF(newType);
+    }
+
+    if (PyErr_Occurred())
+        PyErr_Print();
+
+    for (const auto &cls : std::as_const(cppSelf->d->ast.classes)) {
+        cppSelf->d->classes << cls.name;
+
+        // Create Source type
+        {
+            auto *qobject = new QObject;
+            auto *meta = createAndRegisterSourceFromASTClass(cls, qobject);
+            if (!meta) {
+                delete qobject;
+                PyErr_Format(PyExc_RuntimeError, "Failed to create Source meta object for class '%s'",
+                             cls.name.toUtf8().constData());
+                return -1;
+            }
+
+            auto properties = generateProperties(meta, cls);
+            // Check if an error occurred during generateProperties
+            if (PyErr_Occurred()) {
+                delete qobject;
+                return -1;
+            }
+            auto *propertiesPtr = new QVariantList(properties);
+            auto *pyCapsule = PyCapsule_New(propertiesPtr, nullptr, [](PyObject *capsule) {
+                delete reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(capsule, nullptr));
+            });
+
+            PyTypeObject *newType = createDynamicClass(meta, pyCapsule);
+            if (!newType) {
+                delete qobject;
+                PyErr_Format(PyExc_RuntimeError,
+                             "Failed to create Source Python type for class '%s'",
+                             meta->className());
+                return -1;
+            }
+            if (set_cleanup_capsule_attr_for_pointer(newType, "_qobject_capsule", qobject) < 0) {
+                delete qobject;
+                return -1;
+            }
+
+            PyDict_SetItemString(cppSelf->d->sourceDict, repName(meta),
+                                 reinterpret_cast<PyObject *>(newType));
+            Py_DECREF(newType);
+        }
+
+        // Create Replica type
+        {
+            auto *qobject = new QObject;
+            auto *meta = createAndRegisterReplicaFromASTClass(cls, qobject);
+            if (!meta) {
+                delete qobject;
+                PyErr_Format(PyExc_RuntimeError,
+                             "Failed to create Replica meta object for class '%s'",
+                             qPrintable(cls.name));
+                return -1;
+            }
+
+            auto properties = generateProperties(meta, cls);
+            // Check if an error occurred during generateProperties
+            if (PyErr_Occurred()) {
+                delete qobject;
+                return -1;
+            }
+            auto *propertiesPtr = new QVariantList(properties);
+            auto *pyCapsule = PyCapsule_New(propertiesPtr, nullptr, [](PyObject *capsule) {
+                delete reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(capsule, nullptr));
+            });
+
+            PyTypeObject *newType = createDynamicClass(meta, pyCapsule);
+            if (!newType) {
+                delete qobject;
+                PyErr_Format(PyExc_RuntimeError,
+                             "Failed to create Replica Python type for class '%s'",
+                             meta->className());
+                return -1;
+            }
+            if (set_cleanup_capsule_attr_for_pointer(newType, "_qobject_capsule", qobject) < 0) {
+                delete qobject;
+                return -1;
+            }
+
+            PyDict_SetItemString(cppSelf->d->replicaDict, repName(meta),
+                                 reinterpret_cast<PyObject *>(newType));
+            Py_DECREF(newType);
+        }
+    }
+
+    return 0;
+}
+
+static void RepFile_tp_free(void *self)
+{
+    PySideRepFile *obj = reinterpret_cast<PySideRepFile*>(self);
+    delete obj->d;
+}
+
+/**
+ * @brief Sets the QVariant value based on the provided default value text.
+ *
+ * This function attempts to set the provided QVariant's value based on the
+ * provided text. It evaluates the text as a Python expression, the the python
+ * type associated with the provided QMetaType. It first retrieves the Python
+ * type object corresponding to the given QMetaType, then constructs a Python
+ * expression to instantiate the type with the default value. The expression is
+ * evaluated using PyRun_String, and the result is then set on the QVariant.
+ * Note: The variant is passed by reference and modified in place.
+ *
+ * @return True if the instantiation is successful, false otherwise.
+ */
+bool instantiateFromDefaultValue(QVariant &variant, const QString &defaultValue)
+{
+    auto metaType = variant.metaType();
+    auto *pyType = Shiboken::Conversions::getPythonTypeObject(metaType.name());
+    if (!pyType) {
+        PyErr_Format(PyExc_TypeError, "Failed to find Python type for meta type: %s",
+                     metaType.name());
+        return false;
+    }
+
+    // Evaluate the code
+    static PyObject *pyLocals = PyDict_New();
+
+    // Create the Python expression to evaluate
+    std::string code = std::string(pyType->tp_name) + '('
+                       + defaultValue.toUtf8().constData() + ')';
+    PyObject *pyResult = PyRun_String(code.c_str(), Py_eval_input, pyLocals, pyLocals);
+
+    if (!pyResult) {
+        PyObject *ptype = nullptr;
+        PyObject *pvalue = nullptr;
+        PyObject *ptraceback = nullptr;
+        PyErr_Fetch(&ptype, &pvalue, &ptraceback);
+        PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
+        PyErr_Format(PyExc_TypeError,
+                     "Failed to generate default value. Error: %s. Problematic code: %s",
+                     Shiboken::String::toCString(PyObject_Str(pvalue)), code.c_str());
+        Py_XDECREF(ptype);
+        Py_XDECREF(pvalue);
+        Py_XDECREF(ptraceback);
+        Py_DECREF(pyLocals);
+        return false;
+    }
+
+    Conversions::SpecificConverter converter(metaType.name());
+    if (!converter) {
+        PyErr_Format(PyExc_TypeError, "Failed to find converter from Python type: %s to Qt type: %s",
+                     pyResult->ob_type->tp_name, metaType.name());
+        Py_DECREF(pyResult);
+        return false;
+    }
+    converter.toCpp(pyResult, variant.data());
+    Py_DECREF(pyResult);
+
+    return true;
+}
+
+} // extern "C"
+
+static QVariantList generateProperties(QMetaObject *meta, const ASTClass &astClass)
+{
+    QVariantList properties;
+    auto propertyCount = astClass.properties.size();
+    properties.reserve(propertyCount);
+    for (auto i = 0; i < propertyCount; ++i) {
+        auto j = i + meta->propertyOffset();  // Corresponding property index in the meta object
+        auto metaProperty = meta->property(j);
+        auto metaType = metaProperty.metaType();
+        if (!metaType.isValid()) {
+            PyErr_Format(PyExc_RuntimeError, "Invalid meta type for property %d: %s", i,
+                         astClass.properties[i].type.toUtf8().constData());
+            return {};
+        }
+        auto variant = QVariant(metaType);
+        if (auto defaultValue = astClass.properties[i].defaultValue; !defaultValue.isEmpty()) {
+            auto success = instantiateFromDefaultValue(variant, defaultValue);
+            if (!success) {
+                // Print a warning giving the property name, then propagate the error
+                qWarning() << "Failed to instantiate default value for property: "
+                           << metaProperty.name();
+                return {};
+            }
+        }
+        properties << variant;
+    }
+    return properties;
+}
+
+namespace PySide::RemoteObjects
+{
+
+static const char *RepFile_SignatureStrings[] = {
+    "PySide6.RemoteObjects.RepFile(self,content:str)",
+    nullptr}; // Sentinel
+
+void init(PyObject *module)
+{
+    if (InitSignatureStrings(PySideRepFile_TypeF(), RepFile_SignatureStrings) < 0)
+        return;
+
+    qRegisterMetaType<QRemoteObjectPendingCall>();
+    qRegisterMetaType<QRemoteObjectPendingCallWatcher>();
+
+    Py_INCREF(PySideRepFile_TypeF());
+    PyModule_AddObject(module, "RepFile", reinterpret_cast<PyObject *>(PySideRepFile_TypeF()));
+
+    // Add a test helper to verify type reference counting
+    static PyMethodDef get_capsule_count_def = {
+        "getCapsuleCount",                                 // name of the function in Python
+        reinterpret_cast<PyCFunction>(get_capsule_count),  // C function pointer
+        METH_NOARGS,                                       // flags indicating parameters
+        "Returns the current count of PyCapsule objects"   // docstring
+    };
+
+    PyModule_AddObject(module, "getCapsuleCount", PyCFunction_New(&get_capsule_count_def, nullptr));
+}
+
+} // namespace PySide::RemoteObjects
diff --git a/sources/pyside6/libpysideremoteobjects/pysiderephandler_p.h b/sources/pyside6/libpysideremoteobjects/pysiderephandler_p.h
new file mode 100644 (file)
index 0000000..5956f0b
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_REPHANDLER_P_H
+#define PYSIDE_REPHANDLER_P_H
+
+#include <sbkpython.h>
+
+#include <QtRemoteObjects/repparser.h>
+
+#include <QtCore/qstringlist.h>
+
+struct PySideRepFilePrivate
+{
+    AST ast;
+    PyObject *podDict{};
+    PyObject *replicaDict{};
+    PyObject *sourceDict{};
+    QStringList classes;
+    QStringList pods;
+};
+
+extern "C"
+{
+    extern PyTypeObject *PySideRepFile_TypeF(void);
+
+    // Internal object
+    struct PySideRepFile
+    {
+        PyObject_HEAD
+        PySideRepFilePrivate *d;
+    };
+}; // extern "C"
+
+#endif // PYSIDE_REPHANDLER_P_H
index 4e2e4ad0401e019b45790a6e4a392f159654d82f..b5a00dd422f40d73a78af2fae81b8cc2c3b81e36 100644 (file)
@@ -23,7 +23,7 @@ target_sources(PySidePlugin PRIVATE
 target_compile_definitions(PySidePlugin PRIVATE -DQT_NO_KEYWORDS=1)
 
 if(PYTHON_LIMITED_API)
-    target_compile_definitions(PySidePlugin PRIVATE "-DPy_LIMITED_API=0x03080000")
+    target_compile_definitions(PySidePlugin PRIVATE "-DPy_LIMITED_API=0x03090000")
 endif()
 
 if(CMAKE_BUILD_TYPE STREQUAL "Debug")
index dc8bdf435321bbeafc89c5f0b8b0f6b4a75b1e33..07faa5b02ad3b5de8f010ed784d0f9a993658397 100644 (file)
@@ -6,15 +6,15 @@
 
 #include "designercustomwidgets.h"
 
-#include <QtCore/QCoreApplication>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfoList>
-#include <QtCore/QLoggingCategory>
-#include <QtCore/QOperatingSystemVersion>
-#include <QtCore/QTextStream>
-#include <QtCore/QVariant>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qoperatingsystemversion.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qvariant.h>
 
 #include <string_view>
 #include <utility>
index 2f1db1f31bc2d39c16552a17bf15f5b834331312..0b424666324d3e977d02698a5d930be552765f10 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef PY_DESIGNER_CUSTOM_WIDGETS_H_
 #define PY_DESIGNER_CUSTOM_WIDGETS_H_
 
-#include <QtUiPlugin/QDesignerCustomWidgetCollectionInterface>
+#include <QtUiPlugin/customwidget.h>
 
 // A Qt Designer plugin proxying the QDesignerCustomWidgetCollectionInterface
 // instance set as as a dynamic property on QCoreApplication by the PySide6
index 8142ed5fd4d15d75e5e80cb295567143902b5376..19e285c3acbafd8a4b06eca544fa5441172e7be3 100644 (file)
@@ -2,8 +2,14 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "customwidget.h"
+
 #include <QtCore/qdebug.h>
 
+#include <autodecref.h>
+#include <basewrapper.h>
+#include <bindingmanager.h>
+#include <sbkconverter.h>
+
 // Part of the static plugin linked to the QtUiLoader Python module,
 // allowing it to create a custom widget written in Python.
 PyCustomWidget::PyCustomWidget(PyObject *objectType) :
index 52621f0bd2f0a6983e4c7b43740f2a7fe4976aa7..ef08619e2a13a82bb3edd129c3aaeed6ae3630d1 100644 (file)
@@ -4,9 +4,9 @@
 #ifndef PY_CUSTOM_WIDGET_H_
 #define PY_CUSTOM_WIDGET_H_
 
-#include <shiboken.h>
+#include <sbkpython.h>
 
-#include <QtUiPlugin/QDesignerCustomWidgetInterface>
+#include <QtUiPlugin/customwidget.h>
 
 class PyCustomWidget: public QObject, public QDesignerCustomWidgetInterface
 {
index f67a0847d897349956b14678ce17b88eec0c6cbd..30ec5c907b8a11508b9ef62017912b669ffea389 100644 (file)
@@ -4,9 +4,9 @@
 #ifndef PY_CUSTOM_WIDGETS_H_
 #define PY_CUSTOM_WIDGETS_H_
 
-#include <shiboken.h>
+#include <sbkpython.h>
 
-#include <QtUiPlugin/QDesignerCustomWidgetInterface>
+#include <QtUiPlugin/customwidget.h>
 
 #include <QtCore/qlist.h>
 
index 250b2c292914a7ea19ae31bbefebbd0ca65e60f7..c6446c4cfc67577b89feff1100f95f5ff11f90a9 100644 (file)
@@ -18,7 +18,7 @@ add_library(QtExampleIcons MODULE module.c)
 
 # See libshiboken/CMakeLists.txt
 if(PYTHON_LIMITED_API)
-    target_compile_definitions(QtExampleIcons PRIVATE "-DPy_LIMITED_API=0x03080000")
+    target_compile_definitions(QtExampleIcons PRIVATE "-DPy_LIMITED_API=0x03090000")
 endif()
 
 if(CMAKE_BUILD_TYPE STREQUAL "Debug")
index 0627333e698670dd503be60151b484c2f4d18605..40d4ca00993d339e5d9b9f8e018949453b6dc437 100644 (file)
@@ -11,15 +11,15 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtBluetooth import QBluetoothLocalDevice
+from PySide6.QtBluetooth import QBluetoothLocalDevice  # noqa: E402
 
 
 class QBluetoothLocalDeviceTest(unittest.TestCase):
     def testInitialization(self):
-        device = QBluetoothLocalDevice()
+        device = QBluetoothLocalDevice()  # noqa: F841
 
 
 if __name__ == '__main__':
index 2bc623b7e0fa719e0737ea55c10b7f809ee97c9a..8cfce5e03d39fa28c8e9b736c6d2cb99a9d12021 100644 (file)
@@ -11,13 +11,13 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtCore import QUuid
-from PySide6.QtBluetooth import (QBluetoothUuid, QLowEnergyServiceData,
-                                 QLowEnergyDescriptorData,
-                                 QLowEnergyCharacteristicData)
+from PySide6.QtCore import QUuid  # noqa: E402
+from PySide6.QtBluetooth import (QBluetoothUuid, QLowEnergyServiceData,  # noqa: E402
+                                 QLowEnergyDescriptorData,  # noqa: E402
+                                 QLowEnergyCharacteristicData)  # noqa: E402
 
 
 class QLowEnergyCharacteristicsTest(unittest.TestCase):
index f0228d943e70937ea572902b42ad0448acd285d4..f4c9ded362a85d2752d553bae9e4d180182268d9 100644 (file)
@@ -79,6 +79,8 @@ PYSIDE_TEST(qfile_test.py)
 PYSIDE_TEST(qfileread_test.py)
 PYSIDE_TEST(qflags_test.py)
 PYSIDE_TEST(qinstallmsghandler_test.py)
+PYSIDE_TEST(qiodevice_buffered_read_test.py)
+PYSIDE_TEST(qiopipe_test.py)
 PYSIDE_TEST(qjsondocument_test.py)
 PYSIDE_TEST(qlinef_test.py)
 PYSIDE_TEST(qlocale_test.py)
@@ -104,10 +106,12 @@ PYSIDE_TEST(qoperatingsystemversion_test.py)
 PYSIDE_TEST(qpoint_test.py)
 PYSIDE_TEST(qprocess_test.py)
 PYSIDE_TEST(qproperty_decorator.py)
+PYSIDE_TEST(qrandomgenerator_test.py)
 PYSIDE_TEST(qrect_test.py)
 PYSIDE_TEST(qregularexpression_test.py)
 PYSIDE_TEST(qresource_test.py)
 PYSIDE_TEST(qsettings_test.py)
+PYSIDE_TEST(qsharedmemory_test.py)
 PYSIDE_TEST(qsize_test.py)
 PYSIDE_TEST(qslot_object_test.py)
 PYSIDE_TEST(qsocketnotifier_test.py)
@@ -142,6 +146,7 @@ PYSIDE_TEST(unicode_test.py)
 PYSIDE_TEST(versioninfo_test.py)
 PYSIDE_TEST(loggingcategorymacros_test.py)
 PYSIDE_TEST(qrunnable_test.py)
+PYSIDE_TEST(qmessage_logger_test.py)
 
 if(X11)
     PYSIDE_TEST(qhandle_test.py)
index ca66e0e17ae503912cf3e0d171ae5654eb8e631b..edcfe08eed4f46b076fb863fb89ecb6bdcae4bb0 100644 (file)
@@ -15,10 +15,10 @@ import sys
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1] / "util"))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths()
 
-from PySide6.QtCore import QObject
+from PySide6.QtCore import QObject  # noqa: E402
 
 
 class A(QObject):
index 3d66a6d2c700562dc09020d66bfb91e10c15cfe1..716244ca5269207f25d75e4aee29638d1f3e3f8d 100644 (file)
@@ -32,7 +32,7 @@ exec(demo_coroutine_definition_code)
 
 class CoroutineRaisesStopIterationTestCase(unittest.TestCase):
     def setUp(self):
-        self.coroutine = demo_coroutine()
+        self.coroutine = demo_coroutine()  # noqa: F821
 
     def testCoroutine(self):
         with self.assertRaises(StopIteration):
index a93f9408983d18d719b80fb2fe2c31227251b4c7..7420891fb94fcbb6fb0f7e2067170a2c4f3bf8d2 100644 (file)
@@ -32,7 +32,7 @@ class Bug324(unittest.TestCase):
         self.value = val
 
     def testBug(self):
-        app = QCoreApplication([])
+        app = QCoreApplication([])  # noqa: F841
         bug = QBug()
         self.value = ''
         bug.done.connect(self.on_done)
index 9e910de1fb11932a2c3e02ece5ffe3d71c369294..88d2c66780eb7eed3fbf8266089d6b24dc61fed8 100644 (file)
@@ -23,8 +23,9 @@ class Lock(QMutex):
 class TestBug(unittest.TestCase):
 
     def testCase(self):
-        l = Lock()
+        l = Lock()  # noqa: E741
         l.tryLock()  # this cause a assertion
+        l.unlock()
 
 
 if __name__ == '__main__':
index 1fa04f77f0aa29ffe62477532c188813ca22f554..c9fc3e0cac1bb04f3da7f48fdc4215f1dcbe745f 100644 (file)
@@ -29,7 +29,7 @@ class TestBug706(unittest.TestCase):
         self.start = None
         self.end = None
 
-        app = QCoreApplication([])
+        app = QCoreApplication([])  # noqa: F841
         model = MyModel()
         model.columnsAboutToBeInserted.connect(self.mySlot)
         model.columnsAboutToBeInserted.emit(QModelIndex(), 0, 1)
index 6bf4f261e5c8760e030676aafa9bcc652c94c70a..ecbf0e02dfb94289fbd6171a7bfa55a515b4921b 100644 (file)
@@ -69,7 +69,7 @@ class SetAndGetTestCases(unittest.TestCase):
         f.test = 1
         set_counter = 0
 
-        ret = f.test
+        ret = f.test  # noqa: F841
         self.assertEqual(get_counter, 1)
         self.assertEqual(set_counter, 0)
 
index 0384821f429f131638735b9021cd12eab1c60b74..ae7943ce975ef8df44c54812f88d2c761fef838d 100644 (file)
@@ -70,7 +70,7 @@ class TestDestroyNoConnect(unittest.TestCase):
 
     def testSignalDestroyedinConnect(self):
         # PYSIDE-2328: Connect to signal of temporary
-        with self.assertRaises(RuntimeError):
+        with self.assertRaises(TypeError):
             Foo().s.connect(None)
 
 
index 0e8d759a102ddd92d314101029869ef4ae182b31..ba7ae546a45690c9b6b90bf666d2673252c3a6d0 100644 (file)
@@ -88,7 +88,7 @@ class ErrormessagesWithFeatures(unittest.TestCase):
         print("\n\n" + cm.exception.args[0])
         self.assertTrue(self.probe in cm.exception.args[0])
         with self.assertRaises(TypeError) as cm:
-            qApp.quit_on_last_window_closed = object
+            qApp.quit_on_last_window_closed = object  # noqa: F821
         self.assertTrue(self.probe in cm.exception.args[0])
 
     def testDocIsWorking(self):
index 64f3c0bb3c5cb37f020eb7bee8031ca4d5ac5cb7..5fe96191f45b6601b71949807013f88378a91aa2 100644 (file)
@@ -8,10 +8,10 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtCore import QObject, SIGNAL
+from PySide6.QtCore import QObject, SIGNAL  # noqa: E402
 
 
 class MyObject(QObject):
index 1a31dda22691d4eb5f38d5806e7e76124d58db73..a77bf18605b30005c7d90be00258fb325ae3fd42 100644 (file)
@@ -93,8 +93,8 @@ class FeaturesTest(unittest.TestCase):
                         """), "<string>", "exec"), globals(), edict)
         globals().update(edict)
         feature_list = feature._really_all_feature_names
-        func_list = [tst_bit0, tst_bit1, tst_bit2, tst_bit3,
-                     tst_bit4, tst_bit5, tst_bit6, tst_bit7]
+        func_list = [tst_bit0, tst_bit1, tst_bit2, tst_bit3,  # noqa: F821
+                     tst_bit4, tst_bit5, tst_bit6, tst_bit7]  # noqa: F821
 
         for idx in range(0x100):
             feature.reset()
index e023ee5b57ba1216e21a362aec0b21214711cef1..876340d4fb81b16b990391758beb642b0d3e5ea7 100644 (file)
@@ -11,12 +11,12 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-import datetime
+import datetime  # noqa: E402
 
-from PySide6.QtCore import QTime, QDateTime, QDate
+from PySide6.QtCore import QTime, QDateTime, QDate  # noqa: E402
 
 
 class TestDateTimeConversions (unittest.TestCase):
index f3eec76bfe170073a144cb77527e911f85fc452a..83009631803058a2b2c729e9907cb5a129e6f47d 100644 (file)
@@ -9,10 +9,11 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtCore import QCoreApplication, QParallelAnimationGroup, QSequentialAnimationGroup, QTimer
+from PySide6.QtCore import (QCoreApplication, QParallelAnimationGroup,  # noqa: E402
+                            QSequentialAnimationGroup, QTimer)  # noqa: E402
 
 
 class QAnimationGroupTest(unittest.TestCase):
index a319d604d6387686d45b9c996b92cb365d6dfbc7..7067e45ef20808515c79b273b8ec3b18b1bae346 100644 (file)
@@ -49,7 +49,7 @@ class NullQByteArrayOperatorIAdd(unittest.TestCase, BaseQByteArrayOperatorIAdd):
     '''Test case for operator QByteArray += on null QByteArrays'''
 
     doc_prefix = 'Null object'
-    doc_filter = lambda x: x.startswith('test')
+    doc_filter = lambda x: x.startswith('test')  # noqa: E731
 
     def setUp(self):
         self.obj = QByteArray()
@@ -60,7 +60,7 @@ class ValidQByteArrayOperatorIAdd(unittest.TestCase, BaseQByteArrayOperatorIAdd)
     '''Test case for operator QByteArray += on valid QByteArrays'''
 
     doc_prefix = 'Valid object'
-    doc_filter = lambda x: x.startswith('test')
+    doc_filter = lambda x: x.startswith('test')  # noqa: E731
 
     def setUp(self):
         self.obj = QByteArray(bytes('some byte array', "UTF-8"))
index a20e41be853c7b954061c6f6dbb524036d416fce..257262a4b80f482d314842ba59c6dca2558f40c3 100644 (file)
@@ -4,11 +4,18 @@ from __future__ import annotations
 
 '''Test cases for buffered read methods of QIODevice'''
 
-from PySide6.QtCore import QBuffer
-
 import enum
+import os
+import sys
 import unittest
 
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtCore import QBuffer
+
 
 class TestQIODeviceBufferedRead(unittest.TestCase):
     class TestType(enum.Enum):
index 908a3a892c8d72f3f6910b490631823599d3bab4..0771e6f3b2337a08b2c1852862ec2baa100e87d0 100644 (file)
@@ -4,10 +4,18 @@ from __future__ import annotations
 
 '''Test cases for the QIOPipe class'''
 
-from PySide6.QtCore import QIODevice, QIOPipe
-
+import os
+import sys
 import unittest
 
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+
+from PySide6.QtCore import QIODevice, QIOPipe
+
 
 class QIOPipeTest(unittest.TestCase):
     def setUp(self) -> None:
index 8723cf000a6535cc215946c3beec4d5100ae823e..77f1ebc471a8cfd07426aaa283f38f0bfe7c7d04 100644 (file)
@@ -67,7 +67,7 @@ class QLocaleTestToNumber(unittest.TestCase):
         value = en_locale.toString(3000000000)
         self.assertEqual(value, "3,000,000,000")
         value = en_locale.toString(10e40)
-        self.assertEqual(value, "1E+41")
+        self.assertEqual(value.lower(), "1e+41")
 
 
 if __name__ == '__main__':
index e6e614b2649074d258755431a577b8c35e014d99..46eb0ddea2b54dc848e0fa2f40eebf22cca34067 100644 (file)
@@ -33,7 +33,7 @@ class TestQMessageAuthenticationCode (unittest.TestCase):
         lockFile = QLockFile(self._fileName)
         self.assertTrue(lockFile.lock())
         self.assertTrue(lockFile.isLocked())
-        lock_info = lockFile.getLockInfo();
+        lock_info = lockFile.getLockInfo()
         self.assertEqual(len(lock_info), 3)
         self.assertEqual(lock_info[0], os.getpid())
         lockFile.unlock()
diff --git a/sources/pyside6/tests/QtCore/qmessage_logger_test.py b/sources/pyside6/tests/QtCore/qmessage_logger_test.py
new file mode 100644 (file)
index 0000000..eb2adb1
--- /dev/null
@@ -0,0 +1,74 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import unittest
+import logging
+import io
+import sys
+import os
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtCore import QMessageLogger, QLoggingCategory, QtMsgType
+
+
+class QtMessageHandler(logging.Handler):
+    def __init__(self, category):
+        super().__init__()
+        self.category = category
+
+    def emit(self, record):
+        log_entry = self.format(record)
+        logger = QMessageLogger(__file__, record.lineno, record.funcName)
+
+        if record.levelno == logging.DEBUG:
+            if self.category.isDebugEnabled():
+                logger.debug(self.category, log_entry)
+            else:
+                logger.debug(log_entry)
+
+
+class TestQMessageLogger(unittest.TestCase):
+    def setUp(self):
+        self.logger = logging.getLogger("test_qmessagelogger")
+        self.logger.setLevel(logging.DEBUG)
+        self.stream = io.StringIO()
+        self.capture_handler = logging.StreamHandler(self.stream)
+        self.capture_handler.setLevel(logging.DEBUG)
+        self.logger.addHandler(self.capture_handler)
+
+    def tearDown(self):
+        self.logger.removeHandler(self.capture_handler)
+
+    def test_debug_with_category_enabled(self):
+        category_enabled = QLoggingCategory("test.category.enabled")
+        # 0 is QtDebugMsg
+        category_enabled.setEnabled(QtMsgType.QtDebugMsg, True)
+
+        qt_handler_enabled = QtMessageHandler(category_enabled)
+        self.logger.addHandler(qt_handler_enabled)
+
+        self.logger.debug("Debug with category enabled")
+        self.logger.removeHandler(qt_handler_enabled)
+
+        captured = self.stream.getvalue()
+        self.assertIn("Debug with category enabled", captured)
+
+    def test_debug_with_category_disabled(self):
+        category_disabled = QLoggingCategory("test.category.disabled")
+
+        qt_handler_disabled = QtMessageHandler(category_disabled)
+        self.logger.addHandler(qt_handler_disabled)
+
+        self.logger.debug("Debug with category disabled")
+        self.logger.removeHandler(qt_handler_disabled)
+
+        captured = self.stream.getvalue()
+        self.assertIn("Debug with category disabled", captured)
+
+
+if __name__ == "__main__":
+    unittest.main()
index 4bdd3922bd2f3c2d3075c09bbde898dcdfddf7d4..689e772b0695986a721d93ef674703a9cfcdc549 100644 (file)
@@ -27,9 +27,9 @@ class ChildrenCoreApplication(unittest.TestCase):
         # 2- Create parent and childrens
         # 3- While keeping the children alive, call parent.children()
         # 4- Delete parent
-        app = QCoreApplication([])
+        app = QCoreApplication([])  # noqa: F841
         parent = QObject()
-        children = [QObject(parent) for x in range(25)]
+        children = [QObject(parent) for x in range(25)]  # noqa: F841
         # Uncomment the lines below to make the test pass
         # del children
         # del child2
index 9bb80459aeda14e73eb9f758e8d20e77be30dfd1..febfb0492f3e5ee8f850042462f33884c3f4969d 100644 (file)
@@ -8,10 +8,10 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtCore import QObject
+from PySide6.QtCore import QObject  # noqa: E402
 
 
 class MyObject(QObject):
@@ -24,10 +24,11 @@ class TestDestructor(unittest.TestCase):
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testReference(self):
         o = QObject()
+        base_ref_count = sys.getrefcount(o)
         m = MyObject(o)
-        self.assertEqual(sys.getrefcount(o), 3)
+        self.assertEqual(sys.getrefcount(o), base_ref_count + 1)
         del m
-        self.assertEqual(sys.getrefcount(o), 2)
+        self.assertEqual(sys.getrefcount(o), base_ref_count)
 
 
 if __name__ == '__main__':
index e1121abd7ef9ae7cee0156789f1f6b2effc912ec..4dd026fa0e237ead1a3beffe057229ae75278d99 100644 (file)
@@ -61,7 +61,7 @@ class InheritsCase(unittest.TestCase):
             def __init__(self):
                 self.member = 'member'
                 super().__init__()
-        obj0 = DerivedObject()
+        obj0 = DerivedObject()  # noqa: F841
         # The second instantiation of DerivedObject will generate an exception
         # that will not come to surface immediately.
         obj1 = DerivedObject()
index 1abc2318144a1855171e0cd6a67eec5f239e23bc..ffca0d1770436631c51b13d3274af3b16cfc4716 100644 (file)
@@ -200,14 +200,14 @@ class TestParentOwnership(unittest.TestCase):
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testParentDestructor(self):
         parent = QObject()
-        self.assertEqual(sys.getrefcount(parent), 2)
+        base_ref_count_parent = sys.getrefcount(parent)
 
         child = QObject(parent)
-        self.assertEqual(sys.getrefcount(child), 3)
-        self.assertEqual(sys.getrefcount(parent), 2)
+        base_ref_count_child = sys.getrefcount(child)
+        self.assertEqual(sys.getrefcount(parent), base_ref_count_parent)
 
         del parent
-        self.assertEqual(sys.getrefcount(child), 2)
+        self.assertEqual(sys.getrefcount(child), base_ref_count_child - 1)
 
         # this will fail because parent deleted child cpp object
         self.assertRaises(RuntimeError, lambda: child.objectName())
@@ -216,20 +216,20 @@ class TestParentOwnership(unittest.TestCase):
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testMultipleChildren(self):
         o = QObject()
-        self.assertEqual(sys.getrefcount(o), 2)
+        base_ref_count_o = sys.getrefcount(o)
 
         c = QObject(o)
-        self.assertEqual(sys.getrefcount(c), 3)
-        self.assertEqual(sys.getrefcount(o), 2)
+        base_ref_count_c = sys.getrefcount(c)
+        self.assertEqual(sys.getrefcount(o), base_ref_count_o)
 
         c2 = QObject(o)
-        self.assertEqual(sys.getrefcount(o), 2)
-        self.assertEqual(sys.getrefcount(c), 3)
-        self.assertEqual(sys.getrefcount(c2), 3)
+        self.assertEqual(sys.getrefcount(o), base_ref_count_o)
+        self.assertEqual(sys.getrefcount(c), base_ref_count_c)
+        self.assertEqual(sys.getrefcount(c2), base_ref_count_c)
 
         del o
-        self.assertEqual(sys.getrefcount(c), 2)
-        self.assertEqual(sys.getrefcount(c2), 2)
+        self.assertEqual(sys.getrefcount(c), base_ref_count_c - 1)
+        self.assertEqual(sys.getrefcount(c2), base_ref_count_c - 1)
 
         # this will fail because parent deleted child cpp object
         self.assertRaises(RuntimeError, lambda: c.objectName())
@@ -239,20 +239,20 @@ class TestParentOwnership(unittest.TestCase):
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testRecursiveParent(self):
         o = QObject()
-        self.assertEqual(sys.getrefcount(o), 2)
+        base_ref_count_o = sys.getrefcount(o)
 
         c = QObject(o)
-        self.assertEqual(sys.getrefcount(c), 3)
-        self.assertEqual(sys.getrefcount(o), 2)
+        base_ref_count_c = sys.getrefcount(c)
+        self.assertEqual(sys.getrefcount(o), base_ref_count_o)
 
         c2 = QObject(c)
-        self.assertEqual(sys.getrefcount(o), 2)
-        self.assertEqual(sys.getrefcount(c), 3)
-        self.assertEqual(sys.getrefcount(c2), 3)
+        base_ref_count_c2 = sys.getrefcount(c2)
+        self.assertEqual(sys.getrefcount(o), base_ref_count_o)
+        self.assertEqual(sys.getrefcount(c), base_ref_count_c)
 
         del o
-        self.assertEqual(sys.getrefcount(c), 2)
-        self.assertEqual(sys.getrefcount(c2), 2)
+        self.assertEqual(sys.getrefcount(c), base_ref_count_c - 1)
+        self.assertEqual(sys.getrefcount(c2), base_ref_count_c2 - 1)
 
         # this will fail because parent deleted child cpp object
         self.assertRaises(RuntimeError, lambda: c.objectName())
@@ -262,16 +262,16 @@ class TestParentOwnership(unittest.TestCase):
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testParentTransfer(self):
         o = QObject()
-        self.assertEqual(sys.getrefcount(o), 2)
+        base_ref_count = sys.getrefcount(o)
 
         c = QObject()
-        self.assertEqual(sys.getrefcount(c), 2)
+        self.assertEqual(sys.getrefcount(c), base_ref_count)
 
         c.setParent(o)
-        self.assertEqual(sys.getrefcount(c), 3)
+        self.assertEqual(sys.getrefcount(c), base_ref_count + 1)
 
         c.setParent(None)
-        self.assertEqual(sys.getrefcount(c), 2)
+        self.assertEqual(sys.getrefcount(c), base_ref_count)
 
         del c
         del o
index 37936205e07eccd7d728fc4a4854cbbbcbd233b7..80387ec7782e50bf5c92f927f00c66ab8bfe6992 100644 (file)
@@ -32,6 +32,26 @@ class MyObjectWithNotifyProperty(QObject):
     myProperty = Property(int, readP, fset=writeP, notify=notifyP)
 
 
+class OtherClass:
+    """Helper for QObjectWithOtherClassPropertyTest."""
+    pass
+
+
+class MyObjectWithOtherClassProperty(QObject):
+    """Helper for QObjectWithOtherClassPropertyTest."""
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self._otherclass = None
+
+    def _get_otherclass(self):
+        return self._otherclass
+
+    def _set_otherclass(self, o):
+        self._otherclass = o
+
+    otherclass = Property(OtherClass, fget=_get_otherclass, fset=_set_otherclass)
+
+
 class PropertyWithNotify(unittest.TestCase):
     def called(self):
         self.called_ = True
@@ -50,5 +70,19 @@ class PropertyWithNotify(unittest.TestCase):
         self.assertEqual(o.property("myProperty"), 10)
 
 
+class QObjectWithOtherClassPropertyTest(unittest.TestCase):
+    """PYSIDE-2193: For properties of custom classes not wrapped by shiboken,
+       QVariant<PyObjectWrapper> is used, which had refcount issues causing crashes.
+       Exercise the QVariant conversion by setting and retrieving via the
+       QVariant-based property()/setProperty() API."""
+    def testNotify(self):
+        obj = MyObjectWithOtherClassProperty()
+        obj.setProperty("otherclass", OtherClass())
+        for i in range(10):
+            pv = obj.property("otherclass")
+            print(pv)  # Exercise repr
+            self.assertTrue(type(pv) is OtherClass)
+
+
 if __name__ == '__main__':
     unittest.main()
index 7d6fbc91db9e0063802363211b9650febdbd441e..e42ce5a7d9c2eb63e1aedeb3b7c630daf5acbfd8 100644 (file)
@@ -10,10 +10,10 @@ import weakref
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtCore import QObject, Property
+from PySide6.QtCore import QObject, Property  # noqa: E402
 
 
 class MyObject(QObject):
@@ -39,7 +39,7 @@ class PropertyTest(unittest.TestCase):
     def testDecorator(self):
         self._obDestroyed = False
         o = MyObject()
-        weak = weakref.ref(o, self.destroyCB)
+        weak = weakref.ref(o, self.destroyCB)  # noqa: F841
         o.value = 10
         self.assertEqual(o._value, 10)
         self.assertEqual(o.value, 10)
index 6ae69b6b3b4f05c0ff2ea1a70459b63d73a69674..83b62b5eb5de14a91944d21b4ad01610a27b98a7 100644 (file)
@@ -27,7 +27,7 @@ class QRandomGeneratorTest(unittest.TestCase):
 
     def testGenerator64(self):
         generator = QRandomGenerator64()
-        r = generator.generate()
+        r = generator.generate()  # noqa: F841
 
 
 if __name__ == '__main__':
index 04d1a6fd57def50978c5e76b01ca78706dfaa8a0..561342d4e24125470a6491ac761ba36172279a11 100644 (file)
@@ -4,9 +4,16 @@ from __future__ import annotations
 
 '''Client for the unit test of QSharedMemory'''
 
+import os
 import sys
 
-from PySide6.QtCore import QSharedMemory
+from pathlib import Path
+FILE = Path(__file__).resolve()
+sys.path.append(os.fspath(FILE.parents[1]))
+from init_paths import init_test_paths  # noqa: E402
+init_test_paths(False)
+
+from PySide6.QtCore import QSharedMemory  # noqa: E402
 
 
 def read_string(shared_memory):
index 7310a1b6216970c29d50775a1697418c9eb41cd4..981b5a4353cf3dec55300033af5c90274ff16563 100644 (file)
@@ -8,7 +8,7 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths # noqa: E402
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
 from PySide6.QtCore import Property, QSize
index a12484e8ab5e01d941d47d960724755c13ea30b9..342c3adf0b115ddd57f2d44e3aea5af6ddf8049c 100644 (file)
@@ -22,7 +22,7 @@ class TestVersionInfo(unittest.TestCase):
         self.assertEqual(len(v), 5)
         self.assertEqual(type(v[0]), int)
         self.assertEqual(type(v[1]), int)
-        #self.assertEqual(type(v[2]), int)
+        self.assertEqual(type(v[2]), int)
         self.assertEqual(type(v[3]), str)
         self.assertEqual(type(v[4]), str)
 
index 71a5c7eb9ff1ca56cbbde4fde3bdbabaac097817..fb0df14260337923a7839e375829ae546303f1d0 100644 (file)
@@ -19,7 +19,6 @@ PYSIDE_TEST(bug_PYSIDE-344.py)
 PYSIDE_TEST(deepcopy_test.py)
 PYSIDE_TEST(event_filter_test.py)
 PYSIDE_TEST(float_to_int_implicit_conversion_test.py)
-PYSIDE_TEST(pyside_reload_test.py)
 PYSIDE_TEST(qbrush_test.py)
 PYSIDE_TEST(qcolor_test.py)
 PYSIDE_TEST(qcolor_reduce_test.py)
@@ -36,6 +35,7 @@ if(WIN32)
 endif()
 PYSIDE_TEST(qitemselection_test.py)
 PYSIDE_TEST(qpainter_test.py)
+PYSIDE_TEST(qpaintengine_test.py)
 PYSIDE_TEST(qpen_test.py)
 PYSIDE_TEST(qpdfwriter_test.py)
 PYSIDE_TEST(qpixelformat_test.py)
index e45b40596b402de176498db37335c89ab1f406c5..f53f4ddf76b23f8668ffed9a41115e34531450cb 100644 (file)
@@ -31,7 +31,8 @@ class MyItemModel(QStandardItemModel):
 
 
 class TestBug660(unittest.TestCase):
-    '''QMimeData type deleted prematurely when overriding mime-type in QStandardItemModel drag and drop'''
+    '''QMimeData type deleted prematurely when overriding mime-type in QStandardItemModel
+       drag and drop'''
     def testIt(self):
         model = MyItemModel()
         model.mimeData([model.index(0, 0)])  # if it doesn't raise an exception it's all right!
index bd5ab4ba0b3aade09910be890a966402b0483f75..1a38997af6440a23c21479a77ff6a22cec0e81da 100644 (file)
@@ -3,7 +3,8 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 from __future__ import annotations
 
-'''Test cases for PYSIDE-344, imul/idiv are used instead of mul/div, modifying the argument passed in'''
+'''Test cases for PYSIDE-344, imul/idiv are used instead of mul/div,
+   modifying the argument passed in'''
 
 import os
 import sys
index 57f137fdfcc644a79d91080223bf08245cb5d4af..6fc4c4aa3a6e3d75aa46695ad20cbd139179f585 100644 (file)
@@ -27,15 +27,16 @@ class EventFilter(UsesQApplication):
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testRefCount(self):
         o = QObject()
+        base_ref_count = sys.getrefcount(o)
         filt = MyFilter()
         o.installEventFilter(filt)
-        self.assertEqual(sys.getrefcount(o), 2)
+        self.assertEqual(sys.getrefcount(o), base_ref_count)
 
         o.installEventFilter(filt)
-        self.assertEqual(sys.getrefcount(o), 2)
+        self.assertEqual(sys.getrefcount(o), base_ref_count)
 
         o.removeEventFilter(filt)
-        self.assertEqual(sys.getrefcount(o), 2)
+        self.assertEqual(sys.getrefcount(o), base_ref_count)
 
     def testObjectDestructorOrder(self):
         w = QWindow()
diff --git a/sources/pyside6/tests/QtGui/pyside_reload_test.py b/sources/pyside6/tests/QtGui/pyside_reload_test.py
deleted file mode 100644 (file)
index 83f4ac0..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-from __future__ import annotations
-
-import importlib
-import importlib.util
-import os
-import shutil
-import sys
-import unittest
-
-from pathlib import Path
-sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
-init_test_paths(False)
-
-
-orig_path = os.path.join(os.path.dirname(__file__))
-workdir = os.getcwd()
-src = os.path.normpath(os.path.join(orig_path, '..', 'QtWidgets', 'test_module_template.py'))
-dst = os.path.join(workdir, 'test_module.py')
-shutil.copyfile(src, dst)
-sys.path.append(workdir)
-
-
-def reload_module(moduleName):
-    importlib.reload(moduleName)
-
-
-def increment_module_value():
-    modfile = open(dst, 'a')
-    modfile.write('Sentinel.value += 1' + os.linesep)
-    modfile.flush()
-    modfile.close()
-    if not sys.dont_write_bytecode:
-        import importlib.util
-        cacheFile = importlib.util.cache_from_source(dst)
-        os.remove(cacheFile)
-
-
-class TestModuleReloading(unittest.TestCase):
-
-    def testModuleReloading(self):
-        '''Test module reloading with on-the-fly modifications.'''
-
-        import test_module
-        self.assertEqual(test_module.Sentinel.value, 10)
-
-        increment_module_value()
-        reload_module(sys.modules['test_module'])
-        self.assertEqual(test_module.Sentinel.value, 11)
-
-        reload_module(sys.modules['test_module'])
-        self.assertEqual(test_module.Sentinel.value, 11)
-
-        increment_module_value()
-        reload_module(sys.modules['test_module'])
-        self.assertEqual(test_module.Sentinel.value, 12)
-
-
-if __name__ == "__main__":
-    unittest.main()
index 1971cc555ece2d517a7f3202a3e08a684d53c2e7..363237f2a4c9e90c6c87d71e6d784918417c8b98 100644 (file)
@@ -31,6 +31,11 @@ class QFontTest(UsesQApplication):
         self.assertEqual(len(families), 1)
         self.assertEqual(families[0], font_name)
 
+    def testTagConstruction(self):
+        tag1 = QFont.Tag("head")
+        tag2 = QFont.Tag.fromValue(tag1.value())
+        self.assertEqual(tag1.value(), tag2.value())
+
 
 if __name__ == '__main__':
     unittest.main()
index ac83ee297c9e20db4770bf072c8c2f51692546d2..4847935d506800caf7406de00dae66aecc0ac3f0 100644 (file)
@@ -20,7 +20,7 @@ class QIconCtorWithNoneTest(TimedQGuiApplication):
 
     def testQIconCtorWithNone(self):
         icon = QIcon(None)
-        pixmap = icon.pixmap(48, 48)
+        pixmap = icon.pixmap(48, 48)  # noqa: F841
         self.app.exec()
 
 
diff --git a/sources/pyside6/tests/QtGui/qpaintengine_test.py b/sources/pyside6/tests/QtGui/qpaintengine_test.py
new file mode 100644 (file)
index 0000000..95045fc
--- /dev/null
@@ -0,0 +1,86 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from helper.usesqapplication import UsesQApplication
+from PySide6.QtGui import QPaintEngine, QPainter, QPaintDevice
+from PySide6.QtCore import QPoint, QRect, QLine
+
+
+class PaintEngine(QPaintEngine):
+    def __init__(self):
+        super().__init__()
+        self.line_count = 0
+        self.point_count = 0
+        self.rect_count = 0
+
+    def drawPoints(self, points):
+        self.point_count = len(points)
+
+    def drawRects(self, rects):
+        self.rect_count = len(rects)
+
+    def drawLines(self, lines):
+        self.line_count = len(lines)
+
+    def updateState(self, s):
+        pass
+
+    def begin(self, _dev):
+        return True
+
+    def end(self):
+        return True
+
+
+class PaintDevice(QPaintDevice):
+    def __init__(self):
+        super().__init__()
+        self._engine = PaintEngine()
+
+    def paintEngine(self):
+        return self._engine
+
+    def metric(self, metric):
+        if metric == QPaintDevice.PaintDeviceMetric.PdmDevicePixelRatioScaled:
+            return super().metric(metric)
+        return 1
+
+
+class QPaintEngineTest(UsesQApplication):
+    """PYSIDE-3002: test whether virtual functions of QPaintEngine taking
+       a C-style array of geometry primitives can be overridden."""
+    def setUp(self):
+        super().setUp()
+        self._paint_device = PaintDevice()
+
+    def tearDown(self):
+        self._paint_device = None
+
+    def test(self):
+        points = [QPoint(1, 2), QPoint(3, 4)]
+        rectangles = [QRect(1, 1, 1, 1), QRect(2, 2, 2, 2)]
+        lines = [QLine(1, 2, 3, 4), QLine(3, 4, 5, 6)]
+
+        with QPainter(self._paint_device) as painter:
+            painter.drawPoints(points)
+            painter.drawRects(rectangles)
+            painter.drawLines(lines)
+
+        engine = self._paint_device.paintEngine()
+        self.assertTrue(engine.line_count, 2)
+        self.assertTrue(engine.point_count, 2)
+        self.assertTrue(engine.rect_count, 2)
+
+
+if __name__ == '__main__':
+    unittest.main()
index e78774eec66a01fe91d66405dbbcedd7325b1f3e..ee12478d018d6f6231251d8a5011518fd256503e 100644 (file)
@@ -9,12 +9,12 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtGui import QPixmap
+from PySide6.QtGui import QPixmap  # noqa: E402
 
-from helper.usesqapplication import UsesQApplication
+from helper.usesqapplication import UsesQApplication  # noqa: E402
 
 xpm = [
     "27 22 206 2",
index 9e5c879da287862dfdd16bccbea9ab41474de390..ea121d6a32583119de13c0b51099b2afc8010572 100644 (file)
@@ -19,7 +19,7 @@ from helper.usesqapplication import UsesQApplication
 class QHelpEngineCreation(UsesQApplication):
 
     def testConstructor(self):
-        helpEngine = QHelpEngine('mycollection.qch')
+        helpEngine = QHelpEngine('mycollection.qch')  # noqa: F841
 
 
 if __name__ == '__main__':
index d2297af63dba9b449cda78740e77c17047a97c46..2f94854fc3f3b5083d9191a2d8c575d0ba11bae3 100644 (file)
@@ -10,10 +10,10 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtLocation import QGeoServiceProvider
+from PySide6.QtLocation import QGeoServiceProvider  # noqa: E402
 
 
 class QLocationTestCase(unittest.TestCase):
index a25ab3e3fecf25feed63719d53193f2f3feea094..805ae49c3e39da3afd581b3bd3621b3eec65ee73 100644 (file)
@@ -10,13 +10,13 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from helper.usesqapplication import UsesQApplication
-from PySide6.QtMultimediaWidgets import QGraphicsVideoItem, QVideoWidget
-from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QVBoxLayout, QWidget
-from PySide6.QtCore import QTimer
+from helper.usesqapplication import UsesQApplication  # noqa: E402
+from PySide6.QtMultimediaWidgets import QGraphicsVideoItem, QVideoWidget  # noqa: E402
+from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QVBoxLayout, QWidget  # noqa: E402
+from PySide6.QtCore import QTimer  # noqa: E402
 
 
 class MyWidget(QWidget):
@@ -37,7 +37,7 @@ class QMultimediaWidgetsTest(UsesQApplication):
         w = MyWidget()
         w.show()
 
-        timer = QTimer.singleShot(100, self.app.quit)
+        timer = QTimer.singleShot(100, self.app.quit)  # noqa: F841
         self.app.exec()
 
 
index 4538895eba65ffcccc86a40ed26a0e42f142fa0c..fa9a88b7cb43ef63fe8264d708af88847302be98 100644 (file)
@@ -10,10 +10,10 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtPositioning import QGeoPositionInfoSource
+from PySide6.QtPositioning import QGeoPositionInfoSource  # noqa: E402
 
 
 class QPositioningTestCase(unittest.TestCase):
index 30bf7e78684027013ba7e11a83f7f4138d73caa7..e9e1e664d65b119427056e9991ad7bdbaaf5666a 100644 (file)
@@ -35,3 +35,4 @@ PYSIDE_TEST(javascript_exceptions.py)
 PYSIDE_TEST(qqmlincubator_incubateWhile.py)
 PYSIDE_TEST(qquickitem_grabToImage.py)
 PYSIDE_TEST(signal_arguments.py)
+PYSIDE_TEST(signal_types.py)
index d3ddb625cb976fad13713c7d15c4ca66ebc20ffa..dcd6c92af52deeac0772387d75fd3596bff9b676 100644 (file)
@@ -10,11 +10,11 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtCore import (QCoreApplication, QUrl, QObject, Property)
-from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlAnonymous, QmlElement)
+from PySide6.QtCore import (QCoreApplication, QUrl, QObject, Property)  # noqa: E402
+from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlAnonymous, QmlElement)  # noqa: E402
 
 
 QML_IMPORT_NAME = "grouped"
@@ -85,7 +85,7 @@ def component_error(component):
 
 class TestQmlGroupedProperties(unittest.TestCase):
     def testIt(self):
-        app = QCoreApplication(sys.argv)
+        app = QCoreApplication(sys.argv)  # noqa: F841
         file = Path(__file__).resolve().parent / "groupedproperty.qml"
         url = QUrl.fromLocalFile(file)
         engine = QQmlEngine()
index f53b4f5a35d9bf55b6d7f28aaf084ebe84bc032f..a3344dbfb2e67555f711d0e40c3408270d4656f4 100644 (file)
@@ -8,14 +8,14 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from helper.helper import quickview_errorstring
-from helper.timedqguiapplication import TimedQGuiApplication
-from PySide6.QtCore import QTimer, QUrl
-from PySide6.QtGui import QColor
-from PySide6.QtQuick import QQuickItem, QQuickView
+from helper.helper import quickview_errorstring  # noqa: E402
+from helper.timedqguiapplication import TimedQGuiApplication  # noqa: E402
+from PySide6.QtCore import QTimer, QUrl  # noqa: E402
+from PySide6.QtGui import QColor  # noqa: E402
+from PySide6.QtQuick import QQuickItem, QQuickView  # noqa: E402
 
 
 class TestGrabToSharedPointerImage(TimedQGuiApplication):
index f6784f516cf4cd4ad05b700f335dd015a14b6bb8..81161e58565593286c713121fe8567e45e07631c 100644 (file)
@@ -8,13 +8,13 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtCore import (QCoreApplication, QUrl, QObject, Property)
-from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlAnonymous,
-                           QmlAttached, QmlElement, ListProperty,
-                           qmlAttachedPropertiesObject)
+from PySide6.QtCore import (QCoreApplication, QUrl, QObject, Property)  # noqa: E402
+from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlAnonymous,  # noqa: E402
+                           QmlAttached, QmlElement, ListProperty,  # noqa: E402
+                           qmlAttachedPropertiesObject)  # noqa: E402
 
 
 QML_IMPORT_NAME = "TestLayouts"
@@ -82,7 +82,7 @@ class TestLayout(QObject):
 
 class TestQmlAttached(unittest.TestCase):
     def testIt(self):
-        app = QCoreApplication(sys.argv)
+        app = QCoreApplication(sys.argv)  # noqa: F841
         file = Path(__file__).resolve().parent / 'registerattached.qml'
         url = QUrl.fromLocalFile(file)
         engine = QQmlEngine()
index ee8cb45f2378f40cfddcf70a3abb622ce97833c9..920b638db66bff136e31800eda1e3f6db4fc1717 100644 (file)
@@ -8,13 +8,13 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtCore import (QCoreApplication, QUrl, QObject,
-                            Property)
-from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlExtended,
-                           QmlElement)
+from PySide6.QtCore import (QCoreApplication, QUrl, QObject,  # noqa: E402
+                            Property)  # noqa: E402
+from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlExtended,  # noqa: E402
+                           QmlElement)  # noqa: E402
 
 
 """Test for the QmlExtended decorator. Extends a class TestWidget
@@ -59,7 +59,7 @@ class TestWidget(QObject):
 
 class TestQmlExtended(unittest.TestCase):
     def testIt(self):
-        app = QCoreApplication(sys.argv)
+        app = QCoreApplication(sys.argv)  # noqa: F841
         file = Path(__file__).resolve().parent / 'registerextended.qml'
         url = QUrl.fromLocalFile(file)
         engine = QQmlEngine()
index 9c2b93ca9ddd31c8b6e0525a6ac727a5690d5103..21f1720aad4358173bde3f21f288708a7376c601 100644 (file)
@@ -8,12 +8,12 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtCore import (QCoreApplication, QUrl)
-from PySide6.QtQml import (QQmlComponent, QQmlEngine,
-                           QmlElement, QPyQmlParserStatus)
+from PySide6.QtCore import (QCoreApplication, QUrl)  # noqa: E402
+from PySide6.QtQml import (QQmlComponent, QQmlEngine,  # noqa: E402
+                           QmlElement, QPyQmlParserStatus)  # noqa: E402
 
 
 QML_IMPORT_NAME = "ParserStatus"
@@ -46,7 +46,7 @@ class TestItem(QPyQmlParserStatus):
 
 class TestQmlAttached(unittest.TestCase):
     def testIt(self):
-        app = QCoreApplication(sys.argv)
+        app = QCoreApplication(sys.argv)  # noqa: F841
         file = Path(__file__).resolve().parent / 'registerparserstatus.qml'
         url = QUrl.fromLocalFile(file)
         engine = QQmlEngine()
index 39e5ab75a62b22f96c6f23eeb024100a6f7e8962..18901df9d7b2130c09d44bc443569ba66a81a63d 100644 (file)
@@ -8,18 +8,18 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtCore import QDir, QUrl
-from PySide6.QtGui import QGuiApplication
-from PySide6.QtQml import qmlRegisterType
+from PySide6.QtCore import QDir, QUrl  # noqa: E402
+from PySide6.QtGui import QGuiApplication  # noqa: E402
+from PySide6.QtQml import qmlRegisterType  # noqa: E402
 
 
 class TestQmlSupport(unittest.TestCase):
 
     def testIt(self):
-        app = QGuiApplication([])
+        app = QGuiApplication([])  # noqa: F841
 
         file = os.fspath(Path(__file__).resolve().parent / 'ModuleType.qml')
         url = QUrl.fromLocalFile(QDir.fromNativeSeparators(file))
index e0f24b3e2464c03865b62c9cf75c1ca051de0622..13491ec95300392863befffc824c09ba6e136b96 100644 (file)
@@ -15,7 +15,7 @@ from helper.helper import quickview_errorstring  # noqa: E402
 
 from PySide6.QtCore import Property, QTimer, QUrl, QObject, Slot  # noqa: E402
 from PySide6.QtGui import QGuiApplication  # noqa: E402
-from PySide6.QtQml import (qmlRegisterSingletonType, qmlRegisterSingletonInstance,
+from PySide6.QtQml import (qmlRegisterSingletonType, qmlRegisterSingletonInstance,  # noqa: E402
                            QmlElement, QmlSingleton, QJSValue)  # noqa: E402
 from PySide6.QtQuick import QQuickView  # noqa: E402
 
index 4ecc19da17fb859651897d666df81312a719ea91..069a5011292550658f6433a857de392d642afbd4 100644 (file)
@@ -9,15 +9,15 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from helper.helper import quickview_errorstring
-from helper.timedqguiapplication import TimedQGuiApplication
+from helper.helper import quickview_errorstring  # noqa: E402
+from helper.timedqguiapplication import TimedQGuiApplication  # noqa: E402
 
-from PySide6.QtQuick import QQuickView
-from PySide6.QtCore import QObject, Signal, Slot, QUrl
-from PySide6.QtQml import QmlElement
+from PySide6.QtQuick import QQuickView  # noqa: E402
+from PySide6.QtCore import QObject, Signal, Slot, QUrl  # noqa: E402
+from PySide6.QtQml import QmlElement  # noqa: E402
 
 """PYSIDE-2098: Roundtrip test for signals using QVariantList/QVariantMap.
 
@@ -62,9 +62,9 @@ class Obj(QObject):
         self.dictSignal.emit(test_dict)
 
     @Slot(list)
-    def list_slot(self, l):
-        self._last_data = l
-        print("list_slot", l)
+    def list_slot(self, lst):
+        self._last_data = lst
+        print("list_slot", lst)
 
     @Slot(dict)
     def dict_slot(self, d):
index 2e45960cd715eacdda885d9c466a38116d3dc7c2..8df18daaf6857bff58f70127e8df3d2b28ca5ce8 100644 (file)
@@ -7,13 +7,13 @@ import sys
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[2]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from pathlib import Path
-from PySide6.QtCore import QObject, Slot
-from PySide6.QtQml import QQmlEngine, qmlRegisterType
-from PySide6.QtQuickTest import QUICK_TEST_MAIN_WITH_SETUP
+from pathlib import Path  # noqa: E402
+from PySide6.QtCore import QObject, Slot  # noqa: E402
+from PySide6.QtQml import QQmlEngine, qmlRegisterType  # noqa: E402
+from PySide6.QtQuickTest import QUICK_TEST_MAIN_WITH_SETUP  # noqa: E402
 
 
 """Copy of the equivalent test in qtdeclarative."""
index 2f7cb08b98b3a3f5f2230e7663daf3c24f8e6809..ace1a00fa9040b13b9f2b2599ceb554f767544ea 100644 (file)
@@ -1 +1,11 @@
-# Please add some tests, here
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: BSD-3-Clause
+
+# FIXME:  TypeError: Failed to generate default value. Error: name 'int' is not defined. Problematic code: int(2)
+if(NOT APPLE)
+PYSIDE_TEST(repfile_test.py)
+PYSIDE_TEST(dynamic_types_test.py)
+PYSIDE_TEST(integration_test.py)
+
+add_subdirectory(cpp_interop)
+endif()
diff --git a/sources/pyside6/tests/QtRemoteObjects/cpp_interop/CMakeLists.txt b/sources/pyside6/tests/QtRemoteObjects/cpp_interop/CMakeLists.txt
new file mode 100644 (file)
index 0000000..407a8f8
--- /dev/null
@@ -0,0 +1,25 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects)
+
+add_executable(cpp_interop ${MOC_SOURCES} cpp_interop.cpp)
+set_target_properties(cpp_interop PROPERTIES AUTOMOC ON)
+
+target_link_libraries(cpp_interop PUBLIC
+    Qt6::Core
+    Qt6::RemoteObjects
+)
+
+# Add a custom target to build the C++ program
+add_custom_target(build_cpp_interop
+    COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target cpp_interop
+    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+)
+
+# Exclude windows (see cpp_interop.cpp)
+if(NOT WIN32)
+    PYSIDE_TEST(cpp_interop_test.py)
+endif()
\ No newline at end of file
diff --git a/sources/pyside6/tests/QtRemoteObjects/cpp_interop/cpp_interop.cpp b/sources/pyside6/tests/QtRemoteObjects/cpp_interop/cpp_interop.cpp
new file mode 100644 (file)
index 0000000..8cef3c7
--- /dev/null
@@ -0,0 +1,128 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qsocketnotifier.h>
+#include <QtCore/qtimer.h>
+
+#include <QtRemoteObjects/qremoteobjectreplica.h>
+#include <QtRemoteObjects/qremoteobjectnode.h>
+
+#ifdef Q_OS_WIN
+#  include <QtCore/qt_windows.h>
+#  include <QtCore/qwineventnotifier.h>
+#endif // Q_OS_WIN
+
+#include <iostream>
+
+using namespace Qt::StringLiterals;
+
+class CommandReader : public QObject
+{
+    Q_OBJECT
+public:
+    explicit CommandReader(QObject *parent = nullptr) : QObject(parent)
+    {
+#ifndef Q_OS_WIN
+        auto *notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this);
+        connect(notifier, &QSocketNotifier::activated, this, &CommandReader::handleInput);
+#else
+        // FIXME: Does not work, signals triggers too often, the app is stuck in getline()
+        auto notifier = new QWinEventNotifier(GetStdHandle(STD_INPUT_HANDLE), this);
+        connect(notifier, &QWinEventNotifier::activated, this, &CommandReader::handleInput);
+#endif
+    }
+
+signals:
+    void started();
+
+private slots:
+    void handleInput()
+    {
+        std::string line;
+        if (!std::getline(std::cin, line))
+            return;
+
+        if (line == "quit") {
+            std::cerr << "harness: Received quit. Stopping harness event loop.\n";
+            QCoreApplication::quit();
+        } else if (line == "start") {
+            std::cerr << "harness: Received start. Initializing harness nodes.\n";
+            emit started();
+        } else {
+            std::cerr << "harness: Unknown command \"" << line << "\"\n";
+        }
+    }
+};
+
+class Runner : public QObject
+{
+    Q_OBJECT
+public:
+    Runner(const QUrl &url, const QString &repName, QObject *parent = nullptr)
+        : QObject(parent)
+        , m_url(url)
+        , m_repName(repName)
+    {
+        m_host.setObjectName("cpp_host");
+        if (!m_host.setHostUrl(QUrl("tcp://127.0.0.1:0"_L1))) {
+            qWarning() << "harness: setHostUrl failed: " << m_host.lastError() << m_host.hostUrl();
+            std::cerr << "harness: Fatal harness error.\n";
+            QCoreApplication::exit(-2);
+        }
+
+        m_node.setObjectName("cpp_node");
+        std::cout << "harness: Host url:" << m_host.hostUrl().toEncoded().constData() << '\n';
+        std::cout.flush();
+    }
+
+public slots:
+    void onStart()
+    {
+        m_node.connectToNode(m_url);
+        m_replica.reset(m_node.acquireDynamic(m_repName));
+        if (!m_replica->waitForSource(1000)) {
+            std::cerr << "harness: Failed to acquire replica.\n";
+            QCoreApplication::exit(-1);
+        }
+
+        m_host.enableRemoting(m_replica.get());
+    }
+
+private:
+    QUrl m_url;
+    QString m_repName;
+    QRemoteObjectHost m_host;
+    QRemoteObjectNode m_node;
+    std::unique_ptr<QRemoteObjectDynamicReplica> m_replica;
+};
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication a(argc, argv);
+    if (argc < 3) {
+        std::cerr << "Usage: " << argv[0] << " <url> <name of type>\n";
+        return -1;
+    }
+    QUrl url = QUrl::fromUserInput(QString::fromUtf8(argv[1]));
+    QString repName = QString::fromUtf8(argv[2]);
+
+    if (!url.isValid()) {
+        std::cerr << "Invalid URL: " << argv[1] << '\n';
+        return -1;
+    }
+
+    CommandReader reader;
+    Runner runner(url, repName);
+
+
+    QRemoteObjectNode node;
+    node.setObjectName("cpp_node");
+    std::unique_ptr<QRemoteObjectDynamicReplica> replica;
+
+    QObject::connect(&reader, &CommandReader::started, &runner, &Runner::onStart);
+
+    return QCoreApplication::exec();
+}
+
+#include "cpp_interop.moc"
diff --git a/sources/pyside6/tests/QtRemoteObjects/cpp_interop/cpp_interop_test.py b/sources/pyside6/tests/QtRemoteObjects/cpp_interop/cpp_interop_test.py
new file mode 100644 (file)
index 0000000..2ae4ffb
--- /dev/null
@@ -0,0 +1,197 @@
+#!/usr/bin/python
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+'''Verify Python <--> C++ interop'''
+
+import os
+import sys
+import textwrap
+
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[2]))  # For init_paths
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtCore import QUrl, QProcess, QObject, Signal
+from PySide6.QtRemoteObjects import (QRemoteObjectHost, QRemoteObjectNode, QRemoteObjectReplica,
+                                     RepFile)
+from PySide6.QtTest import QSignalSpy, QTest
+
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))  # For wrap_tests_for_cleanup
+from test_shared import wrap_tests_for_cleanup
+from helper.usesqapplication import UsesQApplication
+
+
+"""
+This test needs to be run from the build directory in
+order to locate the harness binary.
+
+The previous tests all verify Remote Objects integration, but only
+using Python for both Source and Replica.  We need to make sure there
+aren't any surprises in the interplay between Python and C++.
+
+This implements an initial test harness with a C++ app that is
+started by the Python unittest.  We leverage the fact that Remote
+Objects can
+1) Allow remoting any QObject as a Source with enableRemoting
+2) Acquire Dynamic Replicas, where the definition needed for the
+   Replica is sent from the source.
+
+With these, we can create a working C++ app that doesn't need to be
+compiled with any information about the types being used.  We have
+a host node in Python that shares a class derived from a RepFile
+Source type.  The address of this node is passed to the C++ app via
+QProcess, and there a C++ node connects to that address to acquire
+(dynamically) a replica of the desired object.
+
+The C++ code also creates a host node and sends the address/port
+back to Python via the QProcess interface.  Once the Python code
+receives the C++ side address and port, it connects a node to that
+URL and acquires the RepFile based type from Python.
+
+Python        C++
+Host  ----->  Node (Dynamic acquire)
+                |
+                |   Once initialized, the dynamic replica is
+                |   shared (enable_remoting) from the C++ Host
+                |
+Node  <-----  Host
+"""
+
+
+def msg_cannot_start(process, executable):
+    return ('Cannot start "' + executable + '" in "'
+            + os.fspath(Path.cwd()) + '": ' + process.errorString())
+
+
+def stop_process(process):
+    result = process.waitForFinished(2000)
+    if not result:
+        process.kill()
+        result = process.waitForFinished(2000)
+    return result
+
+
+class Controller(QObject):
+    ready = Signal()
+
+    def __init__(self, utest: unittest.TestCase):
+        super().__init__()
+        # Store utest so we can make assertions
+        self.utest = utest
+
+        # Set up nodes
+        self.host = QRemoteObjectHost()
+        self.host.setObjectName("py_host")
+        self.host.setHostUrl(QUrl("tcp://127.0.0.1:0"))
+        self.cpp_url = None
+        self.node = QRemoteObjectNode()
+        self.node.setObjectName("py_node")
+        self._executable = "cpp_interop.exe" if os.name == "nt" else "./cpp_interop"
+
+    def start(self):
+        # Start the C++ application
+        self.process = QProcess()
+        self.process.readyReadStandardOutput.connect(self.process_harness_output)
+        self.process.readyReadStandardError.connect(self.process_harness_stderr_output)
+        urls = self.host.hostUrl().toDisplayString()
+        print(f'Starting C++ application "{self._executable}" "{urls}"', file=sys.stderr)
+        self.process.start(self._executable, [self.host.hostUrl().toDisplayString(), "Simple"])
+        self.utest.assertTrue(self.process.waitForStarted(2000),
+                              msg_cannot_start(self.process, self._executable))
+
+        # Wait for the C++ application to output the host url
+        spy = QSignalSpy(self.ready)
+        self.utest.assertTrue(spy.wait(1000))
+        self.utest.assertTrue(self.cpp_url.isValid())
+
+        self.utest.assertTrue(self.node.connectToNode(self.cpp_url))
+        return True
+
+    def stop(self):
+        if self.process.state() == QProcess.ProcessState.Running:
+            print(f'Stopping C++ application "{self._executable}" {self.process.processId()}',
+                  file=sys.stderr)
+            self.process.write("quit\n".encode())
+            self.process.closeWriteChannel()
+            self.utest.assertTrue(stop_process(self.process))
+        self.utest.assertEqual(self.process.exitStatus(), QProcess.ExitStatus.NormalExit)
+
+    def add_source(self, Source, Replica):
+        """
+        Source and Replica are types.
+
+        Replica is from the rep file
+        Source is a class derived from the rep file's Source type
+        """
+        self.process.write("start\n".encode())
+        source = Source()
+        self.host.enableRemoting(source)
+        replica = self.node.acquire(Replica)
+        self.utest.assertTrue(replica.waitForSource(5000))
+        self.utest.assertEqual(replica.state(), QRemoteObjectReplica.State.Valid)
+        return source, replica
+
+    def process_harness_output(self):
+        '''Process stdout from the C++ application, parse for URL'''
+        output = self.process.readAllStandardOutput().trimmed()
+        lines = output.data().decode().split("\n")
+        HOST_LINE = "harness: Host url:"
+        for line in lines:
+            print("  stdout: ", line, file=sys.stderr)
+            if line.startswith(HOST_LINE):
+                urls = line[len(HOST_LINE):].strip()
+                print(f'url="{urls}"', file=sys.stderr)
+                self.cpp_url = QUrl(urls)
+                self.ready.emit()
+
+    def process_harness_stderr_output(self):
+        '''Print stderr from the C++ application'''
+        output = self.process.readAllStandardError().trimmed()
+        print("  stderr: ", output.data().decode())
+
+
+class HarnessTest(UsesQApplication):
+    def setUp(self):
+        super().setUp()
+        self.rep = RepFile(self.__class__.contents)
+        self.controller = Controller(self)
+        self.assertTrue(self.controller.start())
+
+    def tearDown(self):
+        self.controller.stop()
+        self.app.processEvents()
+        super().tearDown()
+        QTest.qWait(100)  # Wait for 100 msec
+
+
+@wrap_tests_for_cleanup(extra=['rep'])
+class TestBasics(HarnessTest):
+    contents = textwrap.dedent("""\
+        class Simple
+        {
+            PROP(int i = 2);
+            PROP(float f = -1. READWRITE);
+        }
+        """)
+
+    def compare_properties(self, instance, values):
+        '''Compare properties of instance with values'''
+        self.assertEqual(instance.i, values[0])
+        self.assertAlmostEqual(instance.f, values[1], places=5)
+
+    def testInitialization(self):
+        '''Test constructing RepFile from a path string'''
+        class Source(self.rep.source["Simple"]):
+            pass
+        source, replica = self.controller.add_source(Source, self.rep.replica["Simple"])
+        self.compare_properties(source, [2, -1])
+        self.compare_properties(replica, [2, -1])
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/sources/pyside6/tests/QtRemoteObjects/dynamic_types_test.py b/sources/pyside6/tests/QtRemoteObjects/dynamic_types_test.py
new file mode 100644 (file)
index 0000000..5fb828c
--- /dev/null
@@ -0,0 +1,97 @@
+#!/usr/bin/python
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+'''Test cases for dynamic source/replica types'''
+
+import os
+import sys
+import unittest
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtRemoteObjects import RepFile
+
+from test_shared import wrap_tests_for_cleanup
+
+
+contents = """
+class Simple
+{
+    PROP(int i = 2);
+    PROP(float f = -1. READWRITE);
+    SIGNAL(random(int i));
+    SLOT(void reset());
+};
+"""
+
+
+@wrap_tests_for_cleanup(extra=['rep_file'])
+class QDynamicReplicas(unittest.TestCase):
+    '''Test case for dynamic Replicas'''
+
+    def setUp(self):
+        '''Set up test environment'''
+        self.rep_file = RepFile(contents)
+
+    def testDynamicReplica(self):
+        '''Verify that a valid Replica is created'''
+        Replica = self.rep_file.replica["Simple"]
+        self.assertIsNotNone(Replica)
+        replica = Replica()
+        self.assertIsNotNone(replica)
+        self.assertIsNotNone(replica.metaObject())
+        meta = replica.metaObject()
+        self.assertEqual(meta.className(), "Simple")
+        self.assertEqual(meta.superClass().className(), "QRemoteObjectReplica")
+        i = meta.indexOfProperty("i")
+        self.assertNotEqual(i, -1)
+        self.assertEqual(replica.propAsVariant(0), int(2))
+        self.assertEqual(replica.propAsVariant(1), float(-1.0))
+        self.assertEqual(replica.i, int(2))
+        self.assertEqual(replica.f, float(-1.0))
+
+
+@wrap_tests_for_cleanup(extra=['rep_file'])
+class QDynamicSources(unittest.TestCase):
+    '''Test case for dynamic Sources'''
+
+    def setUp(self):
+        '''Set up test environment'''
+        self.rep_file = RepFile(contents)
+        self.test_val = 0
+
+    def on_changed(self, val):
+        self.test_val = val
+
+    def testDynamicSource(self):
+        '''Verify that a valid Source is created'''
+        Source = self.rep_file.source["Simple"]
+        self.assertIsNotNone(Source)
+        source = Source()
+        self.assertIsNotNone(source)
+        self.assertIsNotNone(source.metaObject())
+        meta = source.metaObject()
+        self.assertEqual(meta.className(), "SimpleSource")
+        self.assertEqual(meta.superClass().className(), "QObject")
+        i = meta.indexOfProperty("i")
+        self.assertNotEqual(i, -1)
+        self.assertIsNotNone(source.__dict__.get('__PROPERTIES__'))
+        self.assertEqual(source.i, int(2))
+        self.assertEqual(source.f, float(-1.0))
+        source.iChanged.connect(self.on_changed)
+        source.fChanged.connect(self.on_changed)
+        source.i = 7
+        self.assertEqual(source.i, int(7))
+        self.assertEqual(self.test_val, int(7))
+        source.i = 3
+        self.assertEqual(self.test_val, int(3))
+        source.f = 3.14
+        self.assertAlmostEqual(self.test_val, float(3.14), places=5)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/sources/pyside6/tests/QtRemoteObjects/integration_test.py b/sources/pyside6/tests/QtRemoteObjects/integration_test.py
new file mode 100644 (file)
index 0000000..69b4930
--- /dev/null
@@ -0,0 +1,369 @@
+#!/usr/bin/python
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+'''Test cases for basic Source/Replica communication'''
+
+import os
+import sys
+import textwrap
+import enum
+import gc
+
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtCore import QUrl, qWarning
+from PySide6.QtRemoteObjects import (QRemoteObjectHost, QRemoteObjectNode, QRemoteObjectReplica,
+                                     QRemoteObjectPendingCall, RepFile, getCapsuleCount)
+from PySide6.QtTest import QSignalSpy, QTest
+
+from test_shared import wrap_tests_for_cleanup
+from helper.usesqapplication import UsesQApplication
+
+contents = """
+class Simple
+{
+    PROP(int i = 2);
+    PROP(float f = -1. READWRITE);
+    SIGNAL(random(int i));
+    SLOT(void reset());
+    SLOT(int add(int i));
+};
+"""
+
+
+class QBasicTest(UsesQApplication):
+    '''Test case for basic source/replica communication'''
+    def setUp(self):
+        # Separate output to make debugging easier
+        qWarning(f"\nSet up {self.__class__.__qualname__}")
+        super().setUp()
+        '''Set up test environment'''
+        if hasattr(self.__class__, "contents"):
+            qWarning(f"Using class contents >{self.__class__.contents}<")
+            self.rep = RepFile(self.__class__.contents)
+        else:
+            self.rep = RepFile(contents)
+        self.host = QRemoteObjectHost(QUrl("tcp://127.0.0.1:0"))
+        self.host.setObjectName("host")
+        self.node = QRemoteObjectNode()
+        self.node.setObjectName("node")
+        self.node.connectToNode(self.host.hostUrl())     # pick up the url with the assigned port
+
+    def compare_properties(self, instance, values):
+        '''Compare properties of instance with values'''
+        self.assertEqual(instance.i, values[0])
+        self.assertAlmostEqual(instance.f, values[1], places=5)
+
+    def default_setup(self):
+        '''Set up default test environment'''
+        replica = self.node.acquire(self.rep.replica["Simple"])
+        # Make sure the replica is initialized with default values
+        self.compare_properties(replica, [2, -1])
+        self.assertEqual(replica.isInitialized(), False)
+        source = self.rep.source["Simple"]()
+        # Make sure the source is initialized with default values
+        self.compare_properties(source, [2, -1])
+        return replica, source
+
+    def tearDown(self):
+        self.assertEqual(getCapsuleCount(), 0)
+        self.app.processEvents()
+        super().tearDown()
+        # Separate output to make debugging easier
+        qWarning(f"Tore down {self.__class__.__qualname__}\n")
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class ReplicaInitialization(QBasicTest):
+    def test_ReplicaInitialization(self):
+        replica, source = self.default_setup()
+        source.i = -1
+        source.f = 3.14
+        self.compare_properties(source, [-1, 3.14])
+        init_spy = QSignalSpy(replica.initialized)
+        self.host.enableRemoting(source)
+        self.assertEqual(replica.waitForSource(1000), True)
+        self.assertEqual(replica.state(), QRemoteObjectReplica.State.Valid)
+        # Make sure the replica values are updated to the source values
+        self.compare_properties(replica, [-1, 3.14])
+        self.assertEqual(init_spy.count(), 1)
+        self.assertEqual(replica.isInitialized(), True)
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class SourcePropertyChange(QBasicTest):
+    def test_SourcePropertyChange(self):
+        replica, source = self.default_setup()
+        self.host.enableRemoting(source)
+        self.assertEqual(replica.waitForSource(1000), True)
+        # Make sure the replica values are unchanged since the source had the same values
+        self.compare_properties(replica, [2, -1])
+        source_spy = QSignalSpy(source.iChanged)
+        replica_spy = QSignalSpy(replica.iChanged)
+        source.i = 42
+        self.assertEqual(source_spy.count(), 1)
+        # Make sure the source value is updated
+        self.compare_properties(source, [42, source.f])
+        self.assertTrue(replica_spy.wait(1000))
+        self.assertEqual(replica_spy.count(), 1)
+        # Make sure the replica value is updated
+        self.compare_properties(replica, [42, replica.f])
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class ReplicaPropertyChange(QBasicTest):
+    def test_ReplicaPropertyChange(self):
+        replica, source = self.default_setup()
+        self.host.enableRemoting(source)
+        self.assertEqual(replica.waitForSource(1000), True)
+        # Make sure push methods are working
+        source_spy = QSignalSpy(source.iChanged)
+        replica_spy = QSignalSpy(replica.iChanged)
+        replica.pushI(11)
+        # # Let eventloop run to update the source and verify the values
+        self.assertTrue(source_spy.wait(1000))
+        self.assertEqual(source_spy.count(), 1)
+        self.compare_properties(source, [11, source.f])
+        # Let eventloop run to update the replica and verify the values
+        self.assertTrue(replica_spy.wait(1000))
+        self.assertEqual(replica_spy.count(), 1)
+        self.compare_properties(replica, [11, replica.f])
+
+        # Test setter on replica
+        source_spy = QSignalSpy(source.fChanged)
+        replica_spy = QSignalSpy(replica.fChanged)
+        replica.f = 4.2
+        # Make sure the replica values are ** NOT CHANGED ** since the eventloop hasn't run
+        self.compare_properties(replica, [11, -1])
+        # Let eventloop run to update the source and verify the values
+        self.assertTrue(source_spy.wait(1000))
+        self.assertEqual(source_spy.count(), 1)
+        self.compare_properties(source, [source.i, 4.2])
+        # Let eventloop run to update the replica and verify the values
+        self.assertTrue(replica_spy.wait(1000))
+        self.assertEqual(replica_spy.count(), 1)
+        self.compare_properties(replica, [replica.i, 4.2])
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class DerivedReplicaPropertyChange(QBasicTest):
+    def test_DerivedReplicaPropertyChange(self):
+        # Don't use default_setup(), instead create a derived replica
+        Replica = self.rep.replica["Simple"]
+
+        class DerivedReplica(Replica):
+            pass
+
+        replica = self.node.acquire(DerivedReplica)
+        # Make sure the replica is initialized with default values
+        self.compare_properties(replica, [2, -1])
+        self.assertEqual(replica.isInitialized(), False)
+        source = self.rep.source["Simple"]()
+        self.host.enableRemoting(source)
+        self.assertEqual(replica.waitForSource(1000), True)
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class ReplicaSlotNotImplementedChange(QBasicTest):
+    def test_ReplicaSlotNotImplementedChange(self):
+        replica, source = self.default_setup()
+        self.host.enableRemoting(source)
+        self.assertEqual(replica.waitForSource(1000), True)
+        # Ideally this would fail as the slot is not implemented on the source
+        res = replica.reset()
+        self.assertEqual(type(res), type(None))
+        QTest.qWait(100)  # Wait for 100 ms for async i/o.  There isn't a signal to wait on
+        res = replica.add(5)
+        self.assertEqual(type(res), QRemoteObjectPendingCall)
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class ReplicaSlotImplementedChange(QBasicTest):
+    def test_ReplicaSlotImplementedChange(self):
+        replica = self.node.acquire(self.rep.replica["Simple"])
+        replica.setObjectName("replica")
+
+        class Source(self.rep.source["Simple"]):
+            def __init__(self):
+                super().__init__()
+                self.i = 6
+                self.f = 3.14
+
+            def reset(self):
+                self.i = 0
+                self.f = 0
+
+            def add(self, i):
+                return self.i + i
+
+        source = Source()
+        source.setObjectName("source")
+        self.host.enableRemoting(source)
+        self.assertEqual(replica.waitForSource(1000), True)
+        self.compare_properties(source, [6, 3.14])
+        self.compare_properties(replica, [6, 3.14])
+        replica_spy = QSignalSpy(replica.iChanged)
+        res = replica.reset()
+        self.assertEqual(type(res), type(None))
+        self.assertEqual(replica_spy.wait(1000), True)
+        self.compare_properties(source, [0, 0])
+        self.compare_properties(replica, [0, 0])
+        res = replica.add(5)
+        self.assertEqual(type(res), QRemoteObjectPendingCall)
+        self.assertEqual(res.waitForFinished(1000), True)
+        self.assertEqual(res.returnValue(), 5)
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class RefCountTest(QBasicTest):
+    contents = textwrap.dedent("""\
+        POD MyPOD{
+            ENUM class Position : unsigned short {position1=1, position2=2, position3=4}
+            Position pos,
+            QString name
+        }
+        class Simple
+        {
+            ENUM Position {Left, Right, Top, Bottom}
+            PROP(MyPOD myPod);
+            PROP(Position pos);
+        }
+        """)
+
+    def test_RefCount(self):
+        # Once the rep file is loaded, we should be tracking 4 converter capsules
+        # - 1 for the POD itself
+        # - 1 for the enum in the POD
+        # - 1 for the enum in the Source
+        # - 1 for the enum in the Replica
+        # We should be tracking 3 qobject capsules (POD, Replica, Source)
+        # Note: Source and Replica are distinct types, so Source::EPosition and
+        # Replica::EPosition are distinct as well.
+        # Note 2: The name of the enum ("Position") can be reused for different
+        # types in different classes as shown above.
+        self.assertEqual(getCapsuleCount(), 7)
+        MyPod = self.rep.pod["MyPOD"]
+        self.assertTrue(isinstance(MyPod, type))
+        self.assertTrue(issubclass(MyPod, tuple))
+        MyEnum = MyPod.get_enum("Position")
+        self.assertTrue(isinstance(MyEnum, type))
+        self.assertTrue(issubclass(MyEnum, enum.Enum))
+        e = MyEnum(4)  # noqa: F841
+        Source = self.rep.source["Simple"]
+        source = Source()  # noqa: F841
+        source = None  # noqa: F841
+        Source = None
+        Replica = self.rep.replica["Simple"]
+        replica = self.node.acquire(Replica)  # noqa: F841
+        replica = None  # noqa: F841
+        Replica = None
+        MyEnum = None
+        MyPod = None
+        self.rep = None
+        e = None  # noqa: F841
+        gc.collect()
+        # The enum and POD capsules will only be deleted (garbage collected) if
+        # the types storing them (RepFile, Replica and Source) are garbage
+        # collected first.
+        self.assertEqual(getCapsuleCount(), 0)
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class EnumTest(QBasicTest):
+    contents = textwrap.dedent("""\
+        POD MyPOD{
+            ENUM class Position : unsigned short {position1=1, position2=2, position3=4}
+            Position pos,
+            QString name
+        }
+        class Simple
+        {
+            ENUM Position {Left, Right, Top, Bottom}
+            PROP(MyPOD myPod);
+            PROP(Position pos);
+        }
+        """)
+
+    def test_Enum(self):
+        MyPod = self.rep.pod["MyPOD"]
+        self.assertTrue(isinstance(MyPod, type))
+        self.assertTrue(issubclass(MyPod, tuple))
+        PodEnum = MyPod.get_enum("Position")
+        self.assertTrue(isinstance(PodEnum, type))
+        self.assertTrue(issubclass(PodEnum, enum.Enum))
+        t = (PodEnum(4), "test")
+        myPod = MyPod(*t)
+        with self.assertRaises(ValueError):
+            myPod = MyPod(PodEnum(0), "thing")  # 0 isn't a valid enum value
+        myPod = MyPod(PodEnum(2), "thing")
+        self.assertEqual(myPod.pos, PodEnum.position2)
+        replica = self.node.acquire(self.rep.replica["Simple"])
+        replica.setObjectName("replica")
+        source = self.rep.source["Simple"]()
+        source.setObjectName("source")
+        source.myPod = (PodEnum.position2, "Hello")
+        SourceEnum = source.get_enum("Position")
+        self.assertTrue(isinstance(SourceEnum, type))
+        self.assertTrue(issubclass(SourceEnum, enum.Enum))
+        source.pos = SourceEnum.Top
+        self.assertEqual(source.myPod, (PodEnum.position2, "Hello"))
+        self.assertNotEqual(source.pos, 2)
+        self.host.enableRemoting(source)
+        self.assertEqual(replica.waitForSource(1000), True)
+        self.assertEqual(replica.myPod, (PodEnum.position2, "Hello"))
+        ReplicaEnum = replica.get_enum("Position")
+        # Test invalid comparisons
+        self.assertNotEqual(replica.pos, 2)
+        self.assertNotEqual(replica.pos, SourceEnum.Top)
+        self.assertNotEqual(replica.myPod, (SourceEnum(2), "Hello"))
+        self.assertNotEqual(replica.myPod, (ReplicaEnum(2), "Hello"))
+        self.assertNotEqual(replica.myPod, (2, "Hello"))
+        # Test valid comparisons to Replica enum
+        self.assertEqual(replica.pos, ReplicaEnum.Top)
+        self.assertEqual(replica.myPod, (PodEnum(2), "Hello"))
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class PodTest(QBasicTest):
+    contents = textwrap.dedent("""\
+        POD MyPod(int i, QString s)
+
+        class Simple
+        {
+            PROP(MyPod pod);
+        }
+        """)
+
+    def test_Pod(self):
+        MyPod = self.rep.pod["MyPod"]
+        self.assertTrue(isinstance(MyPod, type))
+        self.assertTrue(issubclass(MyPod, tuple))
+        source = self.rep.source["Simple"]()
+        t = (42, "Hello")
+        pod = MyPod(*t)
+        source.pod = t
+        self.assertEqual(source.pod, t)
+        self.assertEqual(source.pod, pod)
+        source.pod = pod
+        self.assertEqual(source.pod, t)
+        self.assertEqual(source.pod, pod)
+        with self.assertRaises(ValueError):
+            source.pod = (11, "World", "!")
+        with self.assertRaises(TypeError):
+            source.pod = MyPod("Hello", "World")
+        self.assertEqual(source.pod, pod)
+        self.assertTrue(isinstance(pod, MyPod))
+        self.assertEqual(pod.i, 42)
+        self.assertEqual(pod.s, "Hello")
+        self.assertTrue(isinstance(source.pod, MyPod))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/sources/pyside6/tests/QtRemoteObjects/repfile_test.py b/sources/pyside6/tests/QtRemoteObjects/repfile_test.py
new file mode 100644 (file)
index 0000000..b73c84f
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/python
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+'''Test cases for RepFile'''
+
+import os
+import sys
+import unittest
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+from PySide6.QtRemoteObjects import RepFile
+
+from test_shared import wrap_tests_for_cleanup
+
+contents = """
+class Simple
+{
+    PROP(int i = 2);
+    PROP(float f = -1. READWRITE);
+    SIGNAL(random(int i));
+    SLOT(void reset());
+};
+"""
+
+
+@wrap_tests_for_cleanup()
+class QRepFileConstructor(unittest.TestCase):
+    '''Test case for RepFile constructors'''
+    expected = "RepFile(Classes: [Simple], PODs: [])"
+
+    def setUp(self):
+        '''Set up test environment'''
+        self.cwd = Path(__file__).parent
+        self.path = self.cwd / "simple.rep"
+
+    def testRepFileFromPath(self):
+        '''Test constructing RepFile from a path'''
+        with open(self.path, 'r') as f:
+            rep_file = RepFile(f.read())
+        self.assertEqual(str(rep_file), self.expected)
+
+    def testRepFileFromString(self):
+        '''Test constructing RepFile from a string'''
+        rep_file = RepFile(contents)
+        self.assertEqual(str(rep_file), self.expected)
+
+    def testRepFileInvalidString(self):
+        '''Test constructing RepFile from a string'''
+        with self.assertRaises(RuntimeError) as result:
+            RepFile("\n\n}\n\n")
+        self.assertEqual(str(result.exception),
+                         "Error parsing input, line 3: error: Unknown token encountered")
+
+    def testRepFileNoArguments(self):
+        '''Test constructing RepFile with no arguments'''
+        with self.assertRaises(TypeError):
+            RepFile()
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/sources/pyside6/tests/QtRemoteObjects/simple.rep b/sources/pyside6/tests/QtRemoteObjects/simple.rep
new file mode 100644 (file)
index 0000000..7e801a8
--- /dev/null
@@ -0,0 +1,7 @@
+class Simple
+{
+    PROP(int i = 2);
+    PROP(float f = -1. READWRITE);
+    SIGNAL(random(int i));
+    SLOT(void reset());
+};
diff --git a/sources/pyside6/tests/QtRemoteObjects/test_shared.py b/sources/pyside6/tests/QtRemoteObjects/test_shared.py
new file mode 100644 (file)
index 0000000..5b176ce
--- /dev/null
@@ -0,0 +1,126 @@
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import gc
+import sys
+from functools import wraps
+
+
+def _cleanup_local_variables(self, extra, debug):
+    """
+    Function to clean up local variables after a unit test.
+
+    This method will set any local variables defined in the test run to None.  It also
+    sets variables of self to None, if they are provided in the extra list.
+
+    The self argument is passed by the decorator, so we can access the instance variables.
+    """
+    local_vars = self._locals
+    if debug:
+        print(f"  Cleaning up locals: {local_vars.keys()} and member of self: {extra}",
+              file=sys.stderr)
+    exclude_vars = {'__builtins__', 'self', 'args', 'kwargs'}
+    for var in list(local_vars.keys()):
+        if var not in exclude_vars:
+            local_vars[var] = None
+            if debug:
+                print(f"  Set {var} to None", file=sys.stderr)
+    # Remove variables added to 'self' during our test
+    for var in list(vars(self).keys()):
+        if var in extra:
+            setattr(self, var, None)
+            if debug:
+                print(f"  Set self.{var} to None", file=sys.stderr)
+    gc.collect()
+
+
+# This leverages the tip from # https://stackoverflow.com/a/9187022/169296
+# for capturing local variables using sys.setprofile and a tracer function
+def wrap_tests_for_cleanup(extra: str | list[str] = None, debug: bool = False):
+    """
+    Method that returns a decorator for setting variables used in a test to
+    None, thus allowing the garbage collection to clean up properly and ensure
+    destruction behavior is correct.  Using a method to return the decorator
+    allows us to pass extra arguments to the decorator, in this case for extra
+    data members on `self` to set to None or whether to output additional debug
+    logging.
+
+    It simply returns the class decorator to be used.
+    """
+    def decorator(cls):
+        """
+        This is a class decorator that finds and wraps all test methods in a
+        class.
+
+        The provided extra is used to define a set() of variables that are set
+        to None on `self` after the test method has run.  This is useful for
+        making sure the local and self variables can be garbage collected.
+        """
+        _extra = set()
+        if extra:
+            if isinstance(extra, str):
+                _extra.add(extra)
+            else:
+                _extra.update(extra)
+        for name, attr in cls.__dict__.items():
+            if name.startswith("test") and callable(attr):
+                """
+                Only wrap methods that start with 'test' and are callable.
+                """
+                def make_wrapper(method):
+                    """
+                    This is the actual wrapper that will be used to wrap the
+                    test methods.  It will set a tracer function to capture the
+                    local variables and then calls our cleanup function to set
+                    the variables to None.
+                    """
+                    @wraps(method)
+                    def wrapper(self, *args, **kwargs):
+                        if debug:
+                            print(f"wrap_tests_for_cleanup - calling {method.__name__}",
+                                  file=sys.stderr)
+
+                        def tracer(frame, event, arg):
+                            if event == 'return':
+                                self._locals = frame.f_locals.copy()
+
+                        # tracer is activated on next call, return or exception
+                        sys.setprofile(tracer)
+                        try:
+                            # trace the function call
+                            return method(self, *args, **kwargs)
+                        finally:
+                            # disable tracer and replace with old one
+                            sys.setprofile(None)
+                            # call our cleanup function
+                            _cleanup_local_variables(self, _extra, debug)
+                            if debug:
+                                print(f"wrap_tests_for_cleanup - done calling {method.__name__}",
+                                      file=sys.stderr)
+                    return wrapper
+                setattr(cls, name, make_wrapper(attr))
+        return cls
+    return decorator
+
+
+if __name__ == "__main__":
+    # Set up example test class
+    @wrap_tests_for_cleanup(extra="name", debug=True)
+    class test:
+        def __init__(self):
+            self.name = "test"
+
+        def testStuff(self):
+            value = 42
+            raise ValueError("Test")
+            temp = 11  # noqa: F841
+            return value
+
+    t = test()
+    try:
+        t.testStuff()
+    except ValueError:
+        pass
+    # Should print that `value` and `self.name` are set to None, even with the
+    # exception being raised.
index 2f7cb08b98b3a3f5f2230e7663daf3c24f8e6809..9683f6b851c7863d6ef7db25c50d0abb4e341c83 100644 (file)
@@ -1 +1 @@
-# Please add some tests, here
+PYSIDE_TEST(serialbus.py)
diff --git a/sources/pyside6/tests/QtSerialBus/QtSerialBus.pyproject b/sources/pyside6/tests/QtSerialBus/QtSerialBus.pyproject
new file mode 100644 (file)
index 0000000..b00bf39
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["serialbus.py"]
+}
diff --git a/sources/pyside6/tests/QtSerialBus/serialbus.py b/sources/pyside6/tests/QtSerialBus/serialbus.py
new file mode 100644 (file)
index 0000000..20b6e4a
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+'''Test cases for QtSerialBus'''
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths  # noqa: E402
+init_test_paths(False)
+
+from PySide6.QtCore import QFile  # noqa: E402
+from PySide6.QtSerialBus import QCanDbcFileParser  # noqa: E402
+
+
+class QSerialBusTest(unittest.TestCase):
+    def setUp(self):
+        self.dbc_file = Path(__file__).parent / "test.dbc"
+
+    def test_qfile_open(self):
+        f = QFile(str(self.dbc_file))
+        self.assertTrue(f.open(QFile.OpenModeFlag.ReadOnly), msg=f.errorString())
+        f.close()
+
+    def test_qcandbcfileparser_parse(self):
+        parser = QCanDbcFileParser()
+
+        self.assertTrue(parser.parse([str(self.dbc_file)]), msg=parser.errorString())
+
+        self.assertTrue(parser.parse(str(self.dbc_file)), msg=parser.errorString())
+
+        self.assertTrue(parser.parse([str(self.dbc_file.resolve())]), msg=parser.errorString())
+        self.assertTrue(parser.parse(str(self.dbc_file.resolve())), msg=parser.errorString())
+
+        self.assertFalse(parser.parse(["."]), msg=parser.errorString())
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/sources/pyside6/tests/QtSerialBus/test.dbc b/sources/pyside6/tests/QtSerialBus/test.dbc
new file mode 100644 (file)
index 0000000..caf0731
--- /dev/null
@@ -0,0 +1,10 @@
+"""\
+BO_ 399 STEER_STATUS: 7 EPS
+ SG_ STEER_TORQUE_SENSOR : 7|16@0- (-1,0) [-31000|31000] "tbd" EON
+ SG_ STEER_ANGLE_RATE : 23|16@0- (-0.1,0) [-31000|31000] "deg/s" EON
+ SG_ STEER_STATUS : 39|4@0+ (1,0) [0|15] "" EON
+ SG_ STEER_CONTROL_ACTIVE : 35|1@0+ (1,0) [0|1] "" EON
+ SG_ STEER_CONFIG_INDEX : 43|4@0+ (1,0) [0|15] "" EON
+ SG_ COUNTER : 53|2@0+ (1,0) [0|3] "" EON
+ SG_ CHECKSUM : 51|4@0+ (1,0) [0|15] "" EON
+"""
index 21a585ea4614b02552053ba28334547ca1a71c9f..0fb1c29cf59db4b66d30faad857bb1a918dabf64 100644 (file)
@@ -3,7 +3,6 @@
 
 PYSIDE_TEST(click_test.py)
 PYSIDE_TEST(eventfilter_test.py)
-# The test is currently permanently broken, needs to be fixed.
-#PYSIDE_TEST(touchevent_test.py)
+PYSIDE_TEST(touchevent_test.py)
 PYSIDE_TEST(qsignalspy_test.py)
 PYSIDE_TEST(qvalidator_test.py)
index 2e72ec00b9ff4dcbd6f8dd27676d3b794f8ca6cc..797da0aa0706d2cddfe71c7513859c873e7b7416 100644 (file)
@@ -2,7 +2,6 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 from __future__ import annotations
 
-import gc
 import os
 import sys
 import unittest
@@ -16,32 +15,28 @@ from helper.usesqapplication import UsesQApplication
 
 from PySide6.QtWidgets import QWidget
 from PySide6.QtCore import QPoint, QTimer, Qt, QEvent
-from PySide6.QtGui import QTouchDevice
+from PySide6.QtGui import QPointingDevice
 from PySide6.QtTest import QTest
 
 
 class MyWidget(QWidget):
     def __init__(self, parent=None):
-        QWidget.__init__(self, parent)
+        super().__init__(parent)
         self._sequence = []
-        # Fixme (Qt 5): The device needs to be registered (using
-        # QWindowSystemInterface::registerTouchDevice()) for the test to work
-        self._device = QTouchDevice()
+        self._device = QPointingDevice.primaryPointingDevice()
         self.setAttribute(Qt.WA_AcceptTouchEvents)
         QTimer.singleShot(200, self.generateEvent)
 
     def event(self, e):
-        self._sequence.append(e.type())
-        return QWidget.event(self, e)
+        et = e.type()
+        if (et == QEvent.Type.TouchBegin or et == QEvent.Type.TouchUpdate
+                or et == QEvent.Type.TouchEnd):
+            e.accept()
+            self._sequence.append(et)
+            return True
+        return super().event(e)
 
     def generateEvent(self):
-        o = QTest.touchEvent(self, self._device)
-        o.press(0, QPoint(10, 10))
-        o.commit()
-        del o
-        # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
-        gc.collect()
-
         QTest.touchEvent(self, self._device).press(0, QPoint(10, 10))
         QTest.touchEvent(self, self._device).stationary(0).press(1, QPoint(40, 10))
         QTest.touchEvent(self, self._device).move(0, QPoint(12, 12)).move(1, QPoint(45, 5))
@@ -50,12 +45,12 @@ class MyWidget(QWidget):
 
 
 class TouchEventTest(UsesQApplication):
+    @unittest.skipIf(QPointingDevice.primaryPointingDevice() is None, "No device")
     def testCreateEvent(self):
         w = MyWidget()
         w.show()
         self.app.exec()
-        # same values as C++
-        self.assertEqual(w._sequence.count(QEvent.Type.TouchBegin), 2)
+        self.assertEqual(w._sequence.count(QEvent.Type.TouchBegin), 1)
         self.assertEqual(w._sequence.count(QEvent.Type.TouchUpdate), 2)
         self.assertEqual(w._sequence.count(QEvent.Type.TouchEnd), 1)
 
index 08c6f15773cb1eb770735d8ff4d2dda239ecc211..6788ea0f9a7bdfdff037414d2849f15acef74fd4 100644 (file)
@@ -12,5 +12,6 @@ PYSIDE_TEST(bug_913.py)
 PYSIDE_TEST(bug_958.py)
 PYSIDE_TEST(bug_965.py)
 PYSIDE_TEST(bug_1060.py)
+PYSIDE_TEST(loadUiType_test.py)
 PYSIDE_TEST(uiloader_test.py)
 PYSIDE_TEST(ui_test.py)
index 47cbfd706d4ee9f629fa51275fe772f9008547d3..85d38b8934ed465d3e77ed758687096f760c0227 100644 (file)
@@ -19,7 +19,7 @@ from PySide6.QtUiTools import QUiLoader
 class TestBug913 (unittest.TestCase):
 
     def testIt(self):
-        app = QApplication([])
+        app = QApplication([])  # noqa: F841
 
         loader = QUiLoader()
         file = Path(__file__).resolve().parent / 'bug_913.ui'
index 3f0ba8d99617ca199a0a640c01ad96342783e255..a76ddea0399dc4191b831226acf0987161e90762 100644 (file)
@@ -22,7 +22,8 @@ class Gui_Qt(QMainWindow):
 
         lLoader = QUiLoader()
 
-        # this used to cause a segfault because the old inject code used to destroy the parent layout
+        # this used to cause a segfault because the old inject code used to destroy
+        # the parent layout
         file = Path(__file__).resolve().parent / 'bug_958.ui'
         assert (file.is_file())
         self._cw = lLoader.load(file, self)
index 3eddbd294e63d9e356f17b635800d1a2320fd4c1..f706650d73bf8e660b1c82fe453f29aeb98b4d62 100644 (file)
@@ -13,11 +13,14 @@ init_test_paths(False)
 
 from helper.usesqapplication import UsesQApplication
 
+from PySide6.QtCore import QStandardPaths
 from PySide6.QtWidgets import QWidget, QFrame, QPushButton
 from PySide6.QtUiTools import loadUiType
 
 
 class loadUiTypeTester(UsesQApplication):
+
+    @unittest.skipUnless(bool(QStandardPaths.findExecutable("pyside6-uic")), "pyside6-uic missing")
     def testFunction(self):
         filePath = os.path.join(os.path.dirname(__file__), "minimal.ui")
         loaded = loadUiType(filePath)
index 01b7d08ea258b2e9be3f67c479d280379e8e7aed..ca6831e071d69211a10fc3a13cfec283a23bc573 100644 (file)
@@ -76,6 +76,7 @@ PYSIDE_TEST(missing_symbols_test.py)
 PYSIDE_TEST(paint_event_test.py)
 PYSIDE_TEST(parent_method_test.py)
 PYSIDE_TEST(private_mangle_test.py)
+PYSIDE_TEST(pyside_reload_test.py)
 PYSIDE_TEST(python_properties_test.py)
 PYSIDE_TEST(qabstracttextdocumentlayout_test.py)
 PYSIDE_TEST(qaccessible_test.py)
@@ -84,10 +85,10 @@ PYSIDE_TEST(qapp_issue_585.py)
 PYSIDE_TEST(qapp_test.py)
 PYSIDE_TEST(qapplication_test.py)
 PYSIDE_TEST(qapplication_exit_segfault_test.py)
+PYSIDE_TEST(pyside3069.py)
 PYSIDE_TEST(qdialog_test.py)
 PYSIDE_TEST(qdynamic_signal.py)
-# TODO: This passes, but requires manual button clicking (at least on mac)
-#PYSIDE_TEST(qfontdialog_test.py)
+PYSIDE_TEST(qfontdialog_test.py)
 PYSIDE_TEST(qformlayout_test.py)
 PYSIDE_TEST(qgraphicsitem_test.py)
 PYSIDE_TEST(qgraphicsitem_isblocked_test.py)
@@ -128,7 +129,6 @@ PYSIDE_TEST(qcolormap_test.py)
 PYSIDE_TEST(reference_count_test.py)
 PYSIDE_TEST(signature_test.py)
 PYSIDE_TEST(standardpixmap_test.py)
-PYSIDE_TEST(test_module_template.py)
 PYSIDE_TEST(virtual_protected_inheritance_test.py)
 PYSIDE_TEST(virtual_pure_override_test.py)
 PYSIDE_TEST(wrong_return_test.py)
index c81871df4686f9d6e8d8b2ff7d0e9529290f43bf..2c09f866509c285db571fcd8c746d29e2e8b6faa 100644 (file)
@@ -10,11 +10,11 @@ import weakref
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtWidgets import QMenu, QWidget, QMenuBar, QToolBar
-from helper.usesqapplication import UsesQApplication
+from PySide6.QtWidgets import QMenu, QWidget, QMenuBar, QToolBar  # noqa: E402
+from helper.usesqapplication import UsesQApplication  # noqa: E402
 
 
 class TestQActionLifeCycle(UsesQApplication):
@@ -26,7 +26,7 @@ class TestQActionLifeCycle(UsesQApplication):
         w = QWidget()
         menu = QMenu(w)
         act = menu.addAction("MENU")
-        _ref = weakref.ref(act, self.actionDestroyed)
+        _ref = weakref.ref(act, self.actionDestroyed)  # noqa: F841
         act = None
         self.assertFalse(self._actionDestroyed)
         menu.clear()
@@ -39,7 +39,7 @@ class TestQActionLifeCycle(UsesQApplication):
         w = QWidget()
         menuBar = QMenuBar(w)
         act = menuBar.addAction("MENU")
-        _ref = weakref.ref(act, self.actionDestroyed)
+        _ref = weakref.ref(act, self.actionDestroyed)  # noqa: F841
         act = None
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
@@ -54,7 +54,7 @@ class TestQActionLifeCycle(UsesQApplication):
         w = QWidget()
         toolBar = QToolBar(w)
         act = toolBar.addAction("MENU")
-        _ref = weakref.ref(act, self.actionDestroyed)
+        _ref = weakref.ref(act, self.actionDestroyed)  # noqa: F841
         act = None
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
index a8f836ce53515080762f10c6d6420a9e9278cc00..bcf3ffe7a285394fc8f77b0a9805612a2c78e033 100644 (file)
@@ -22,10 +22,10 @@ class TestBug1002 (UsesQApplication):
     def testReturnWindow(self):
         widget = QWidget()
         button = QPushButton(widget)
-        self.assertEqual(sys.getrefcount(widget), 2)
+        base_ref_count = sys.getrefcount(widget)
         window = button.window()
-        self.assertEqual(sys.getrefcount(widget), 3)
-        self.assertEqual(sys.getrefcount(window), 3)
+        self.assertEqual(sys.getrefcount(widget), base_ref_count + 1)
+        self.assertEqual(sys.getrefcount(window), base_ref_count + 1)
 
         del widget
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
index 9baf85def19d3c08eab231cd2d388d66d68b650e..843b1a7fe2086326636490f56eafc97db35f1475 100644 (file)
@@ -73,27 +73,28 @@ class TestBug1006 (TimedQApplication):
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testRemoveOrphanWidget(self):
         widget = QLabel()
+        base_ref_count = sys.getrefcount(widget)
         layout = QHBoxLayout()
         layout.addWidget(widget)
-        self.assertEqual(sys.getrefcount(widget), 3)
+        self.assertEqual(sys.getrefcount(widget), base_ref_count + 1)
 
         layout.removeWidget(widget)
         widget.setObjectName("MyWidget")
-        self.assertEqual(sys.getrefcount(widget), 2)
+        self.assertEqual(sys.getrefcount(widget), base_ref_count)
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testRemoveChildWidget(self):
         parent = QLabel()
         widget = QLabel(parent)
-        self.assertEqual(sys.getrefcount(widget), 3)
+        base_ref_count = sys.getrefcount(widget)
 
         layout = QHBoxLayout()
         layout.addWidget(widget)
-        self.assertEqual(sys.getrefcount(widget), 3)
+        self.assertEqual(sys.getrefcount(widget), base_ref_count)
 
         layout.removeWidget(widget)
         widget.setObjectName("MyWidget")
-        self.assertEqual(sys.getrefcount(widget), 3)
+        self.assertEqual(sys.getrefcount(widget), base_ref_count)
 
 
 if __name__ == "__main__":
index 4edad18709c55ff4aeecda8217b52629798d8a64..ff8c3248f67e1f84b8ffb6f0e580bb5dce8e3b82 100644 (file)
@@ -17,6 +17,6 @@ from PySide6.QtWidgets import QApplication, QGridLayout, QWidget
 a = QApplication([])
 
 w = QWidget()
-l = QGridLayout(w)
+layout = QGridLayout(w)
 
-l.itemAtPosition(0, 0)
+layout.itemAtPosition(0, 0)
index 7987be96e69f0f5dffb4822516b31446aa13a77f..3c47b44836fa6e90c4f99d3eaebedf0919943a23 100644 (file)
@@ -19,10 +19,10 @@ from PySide6.QtWidgets import QApplication, QMainWindow, QLayout
 class QAppPresence(unittest.TestCase):
 
     def testBug(self):
-        app = QApplication(sys.argv)
+        app = QApplication(sys.argv)  # noqa: F841
         window = QMainWindow()
-        l = window.layout()
-        self.assertTrue(isinstance(l, QLayout))
+        layout = window.layout()
+        self.assertTrue(isinstance(layout, QLayout))
 
 
 if __name__ == '__main__':
index 463c20527fc52459d5e8e4efe9b0fd2cc616d158..3cdd64787c72a0329f3c226e789e8e2ac7e1d912 100644 (file)
@@ -26,7 +26,7 @@ class DiagramItem(QGraphicsPolygonItem):
 
 class BugTest(unittest.TestCase):
     def test(self):
-        app = QApplication(sys.argv)
+        app = QApplication(sys.argv)  # noqa: F841
         scene = QGraphicsScene()
         item = DiagramItem()
         item2 = DiagramItem()
index 5adcacccce57a2754914fd2f012f4b8e943469e9..6d5c4333a6d89285b73f3558edf8206ebb3381fd 100644 (file)
@@ -26,4 +26,4 @@ a = QApplication(sys.argv)
 t = Test()
 t.show()
 QTimer.singleShot(0, t.close)
-sys.exit(a.exec_())
+sys.exit(a.exec())
index 7775ed1b6bc4c8788071b589020e9e8d3b20ab65..81cee86d16d4f9161ff805f7d93512d89aa6ee5b 100644 (file)
@@ -26,7 +26,7 @@ class TestMenuDerivedClass(unittest.TestCase):
         pass
 
     def testConnectSignal(self):
-        app = QApplication([])
+        app = QApplication([])  # noqa: F841
         m2 = M2()
         # Test if the aboutToShow signal was translated to correct type
         m2.aboutToShow.connect(self.aboutToShowHandler)
index c5e054c3ff622b8744eedcc49bf93e022ae0fd9c..9bcd9d40c74f686682e8bac05c6a4f8530629371 100644 (file)
@@ -18,7 +18,7 @@ class TestBug546(unittest.TestCase):
 
     """Test to check a crash at exit"""
     def testIt(self):
-        app = QApplication([])
+        app = QApplication([])  # noqa: F841
         textEdit = QPlainTextEdit()
         completer = QCompleter(("foo", "bar"), textEdit)
         completer.setWidget(textEdit)
index 376c15ee8c99a622cd6d89313104d2967a1f0071..3d8581201c3d163f9629eebd358eee9fc06ba9a3 100644 (file)
@@ -22,7 +22,6 @@ from PySide6.QtWidgets import QApplication, QPushButton, QWidget
 class Bug576(unittest.TestCase):
     def onButtonDestroyed(self, button):
         self._destroyed = True
-        self.assertTrue(isinstance(button, QPushButton))
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testWidgetParent(self):
@@ -32,13 +31,13 @@ class Bug576(unittest.TestCase):
 
         b = QPushButton("test")
         b.destroyed[QObject].connect(self.onButtonDestroyed)
-        self.assertEqual(sys.getrefcount(b), 2)
+        base_ref_count = sys.getrefcount(b)
         b.setParent(w)
-        self.assertEqual(sys.getrefcount(b), 3)
+        self.assertEqual(sys.getrefcount(b), base_ref_count + 1)
         b.parent()
-        self.assertEqual(sys.getrefcount(b), 3)
+        self.assertEqual(sys.getrefcount(b), base_ref_count + 1)
         b.setParent(None)
-        self.assertEqual(sys.getrefcount(b), 2)
+        self.assertEqual(sys.getrefcount(b), base_ref_count)
         del b
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
index c3e0ed83a32ae62720f06a8d95c607cfe035c38a..aa7153df24ab3722a17fa50a4db41c767492ce9e 100644 (file)
@@ -15,9 +15,10 @@ from PySide6.QtWidgets import QStyleOptionGraphicsItem
 
 
 class Bug640(unittest.TestCase):
+    """crash!?"""
     def testIt(self):
         option = QStyleOptionGraphicsItem()
-        a = option.state  # crash!?
+        a = option.state  # noqa: F841
 
 
 if __name__ == "__main__":
index 370e9474b5c46a65980aacb8025c7c586b5e4c2c..38ab0c8a65ab8138da81c66c0e499bc149e1b804 100644 (file)
@@ -17,7 +17,7 @@ from PySide6.QtWidgets import QApplication, QWizard, QWizardPage
 class TestBug653(unittest.TestCase):
     """Crash after calling QWizardPage.wizard()"""
     def testIt(self):
-        app = QApplication([])
+        app = QApplication([])  # noqa: F841
 
         wizard = QWizard()
         page = QWizardPage()
index 0b2de8f043702789c54e983b5fec171e51a9f24e..fba0789ca0169993239231f732e590355998f72f 100644 (file)
@@ -25,7 +25,7 @@ class A(QMainWindow):
         v.setModel(a)
         self.setCentralWidget(v)
         # Test index() method (see PYSIDE-570, PYSIDE-331)
-        index = a.index(0, 0, QModelIndex())
+        index = a.index(0, 0, QModelIndex())  # noqa: F841
 
 
 app = QApplication([])
index 27a00ad457f5d064020da2a1987d96bdb043967f..96a5ec441b2967c85d6be5fcf46f02561bf6d539 100644 (file)
@@ -22,11 +22,11 @@ class TestBug679(unittest.TestCase):
 
         scene = QGraphicsScene()
         hello = scene.addText("Hello")
+        base_ref_count = sys.getrefcount(hello)
         scene.addText("World")
 
-        self.assertEqual(sys.getrefcount(hello), 3)
         scene.clear()
-        self.assertEqual(sys.getrefcount(hello), 2)
+        self.assertEqual(sys.getrefcount(hello), base_ref_count - 1)
         self.assertEqual(len(scene.items()), 0)
         self.assertRaises(RuntimeError, hello.isVisible)  # the C++ object was deleted
 
index 23dd752ad2141df059558ab8a00fef876a60e5bd..8a2991c6fceb3d39b9a9e558baed672e17db3c4a 100644 (file)
@@ -28,7 +28,7 @@ class MyModel (QAbstractListModel):
 
 class TestBug693(unittest.TestCase):
     def testIt(self):
-        app = QApplication([])
+        app = QApplication([])  # noqa: F841
         model = MyModel()
         view = QListView()
         view.setModel(model)
index 074f569f6762e254ad37abd0b45d02aefa3c2ef3..61356c17365ed13b1968048e6dbde98ccfcaccd7 100644 (file)
@@ -13,24 +13,28 @@ init_test_paths(False)
 
 from helper.usesqapplication import UsesQApplication
 
-from PySide6.QtCore import QTimer
+from PySide6.QtCore import QCoreApplication, QTimer
 from PySide6.QtGui import QPainter
 from PySide6.QtWidgets import QWidget
 
 
 class MyWidget(QWidget):
+    def __init__(self):
+        super().__init__()
+        self._info = None
+
     def paintEvent(self, e):
-        p = QPainter(self)
-        self._info = p.fontInfo()
-        self._app.quit()
+        with QPainter(self) as p:
+            self._info = p.fontInfo()
+        QTimer.singleShot(0, qApp.quit)  # noqa: F821
 
 
 class TestQPainter(UsesQApplication):
     def testFontInfo(self):
         w = MyWidget()
-        w._app = self.app
-        w._info = None
-        QTimer.singleShot(300, w.show)
+        w.show()
+        while not w.windowHandle().isExposed():
+            QCoreApplication.processEvents()
         self.app.exec()
         self.assertTrue(w._info)
 
index eb2c587118694f837d8c469effd4e2891ce00853..c4fe135410d1c049fd513200506ce1af3c011d24 100644 (file)
@@ -62,7 +62,7 @@ class TestRepr (unittest.TestCase):
 
     def testIt(self):
 
-        app = QApplication([])
+        app = QApplication([])  # noqa: F841
 
         self.assertEqual("<__main__.MyQObject(0x", repr(MyQObject())[:22])
         self.assertEqual("<__main__.MyQWidget(0x", repr(MyQWidget())[:22])
index 6f2fc128dbdc6958e479ac2a0f07796d395c58f9..a95d3aa37aa817b288dfd4ad5e937a24ed905dfa 100644 (file)
@@ -26,10 +26,11 @@ class MyWidget(QPushButton):
         self.paintReceived.emit()
 
     def paintEvent(self, e):
-        p = QPainter(self)
-        style = QApplication.style()
-        option = QStyleOptionButton()
-        style.drawControl(QStyle.ControlElement.CE_PushButton, option, p)
+        with QPainter(self) as p:
+            style = QApplication.style()
+            option = QStyleOptionButton()
+            self.initStyleOption(option)
+            style.drawControl(QStyle.ControlElement.CE_PushButton, option, p)
         self._painted = True
         QTimer.singleShot(0, self._emitPainted)
 
index f5972d335727eeda714d5629697b5cc07de851ef..7c9999323bffa158b979af203c84c891a6d9c1a0 100644 (file)
@@ -14,9 +14,14 @@ init_test_paths(False)
 from PySide6.QtCore import Qt
 from PySide6.QtWidgets import QApplication, QHeaderView
 
+received_column = None
+received_order = None
 
-def foo(a, b):
-    pass
+
+def foo(colum, order):
+    global received_column, received_order
+    received_column = colum
+    received_order = order
 
 
 class TestBug941 (unittest.TestCase):
@@ -26,7 +31,9 @@ class TestBug941 (unittest.TestCase):
         view = QHeaderView(Qt.Orientation.Horizontal)
         self.assertTrue(view.sortIndicatorChanged.connect(foo))
         # this can't raise an exception!
-        view.sortIndicatorChanged.emit(0, Qt.Orientation.Vertical)
+        view.sortIndicatorChanged.emit(0, Qt.SortOrder.DescendingOrder)
+        self.assertEqual(received_column, 0)
+        self.assertEqual(received_order, Qt.SortOrder.DescendingOrder)
 
 
 if __name__ == '__main__':
index d254f2877fdd3f1ea13bc327cb01c52e26fcab32..0923e5d1ac1d27ce6acbf63b71ab62c7e4d61b7f 100644 (file)
@@ -21,7 +21,7 @@ class TestBug967 (unittest.TestCase):
 
     def testIt(self):
         self.arg = None
-        app = QApplication([])
+        app = QApplication([])  # noqa: F841
         obj = QComboBox()
         obj.currentIndexChanged.connect(self.callback)
         obj.currentIndexChanged.emit(5)
index 0fd582206e85d8c629b6a6b173e7ff2ddbc4f54e..19aa29809530f0fd823d252b06a141d689879e65 100644 (file)
@@ -21,7 +21,7 @@ class TestBug988 (unittest.TestCase):
 
     def testIt(self):
         self.arg = None
-        app = QApplication([])
+        app = QApplication([])  # noqa: F841
         obj = QTabWidget()
         obj.currentChanged.connect(self.callback)
         obj.currentChanged.emit(5)
index d2d236dbb510588059dd15e859ce243e36fcda20..6f2413159c52e8fafdbf4a195f8de15a862d0287 100644 (file)
@@ -17,7 +17,7 @@ from PySide6.QtWidgets import QApplication
 class TestBug998 (unittest.TestCase):
     def testNoFocusWindow(self):
         widget = QApplication.focusWidget()
-        self.assertTrue(widget == None)
+        self.assertTrue(widget is None)
 
 
 if __name__ == '__main__':
diff --git a/sources/pyside6/tests/QtWidgets/pyside3069.py b/sources/pyside6/tests/QtWidgets/pyside3069.py
new file mode 100644 (file)
index 0000000..62ad730
--- /dev/null
@@ -0,0 +1,51 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths  # noqa: E402
+init_test_paths(False)
+
+from PySide6.QtCore import Qt  # noqa: E402
+from PySide6.QtWidgets import QApplication, QComboBox, QGraphicsScene, QGraphicsView  # noqa: E402
+
+from helper.usesqapplication import UsesQApplication  # noqa: E402
+
+
+class BugTest(UsesQApplication):
+    """PYSIDE-3069: Test that the conversion of an element of a list
+       QGraphicsItem* to QGraphicsProxyWidget* (inheriting QObject/QGraphicsItem)
+       works correctly without crash.
+
+       For this, we need a QGraphicsProxyWidget for which no wrapper exists,
+       created in C++. So, we populate a combo, add it to the scene and show its
+       popup, which creates a top level that is automatically wrapped by
+       another QGraphicsProxyWidget. This, we print from the list of items().
+
+       See also PYSIDE-86, PYSIDE-1887."""
+    def test(self):
+        qApp.setEffectEnabled(Qt.UI_AnimateCombo, False)  # noqa: F821
+        cb = QComboBox()
+        cb.addItem("i1")
+        cb.addItem("i2")
+        scene = QGraphicsScene()
+        scene.addWidget(cb)
+        view = QGraphicsView(scene)
+        view.show()
+        cb.showPopup()
+        while not view.windowHandle().isExposed():
+            QApplication.processEvents()
+        items = scene.items()
+        self.assertEqual(len(items), 2)  # Combo and its popup, created in C++
+        for i in items:
+            print(i)
+        view.close()
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/sources/pyside6/tests/QtWidgets/pyside_reload_test.py b/sources/pyside6/tests/QtWidgets/pyside_reload_test.py
new file mode 100644 (file)
index 0000000..d091d15
--- /dev/null
@@ -0,0 +1,62 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import importlib
+import importlib.util
+import os
+import sys
+import unittest
+
+from tempfile import TemporaryDirectory
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+
+def reload_module(moduleName):
+    importlib.reload(moduleName)
+
+
+class TestModuleReloading(unittest.TestCase):
+    def setUp(self):
+        orig_path = Path(__file__).resolve().parent
+        self._src = orig_path / 'test_module_template.py'
+        self._workdir = TemporaryDirectory()
+        self._dst = Path(self._workdir.name) / 'test_module.py'
+        self._dst.write_bytes(self._src.read_bytes())
+        sys.path.append(self._workdir.name)
+
+    def tearDown(self):
+        sys.path.remove(self._workdir.name)
+        self._workdir = None
+
+    def _increment_module_value(self):
+        with self._dst.open(mode='a') as modfile:
+            modfile.write('Sentinel.value += 1\n')
+        if not sys.dont_write_bytecode:
+            import importlib.util
+            cacheFile = importlib.util.cache_from_source(os.fspath(self._dst))
+            os.remove(cacheFile)
+
+    def testModuleReloading(self):
+        '''Test module reloading with on-the-fly modifications.'''
+
+        import test_module
+        self.assertEqual(test_module.Sentinel.value, 10)
+
+        self._increment_module_value()
+        reload_module(sys.modules['test_module'])
+        self.assertEqual(test_module.Sentinel.value, 11)
+
+        reload_module(sys.modules['test_module'])
+        self.assertEqual(test_module.Sentinel.value, 11)
+
+        self._increment_module_value()
+        reload_module(sys.modules['test_module'])
+        self.assertEqual(test_module.Sentinel.value, 12)
+
+
+if __name__ == "__main__":
+    unittest.main()
index ec8a4756931f369070a9f2cb2693a1cc38899817..1300ea4aa0fa9a4c6e13e7afba12f9ca86c10a48 100644 (file)
@@ -40,15 +40,15 @@ import sys
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1] / "util"))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths()
 
-from PySide6.QtCore import QTimer
-from PySide6.QtWidgets import QApplication
+from PySide6.QtCore import QTimer  # noqa: E402
+from PySide6.QtWidgets import QApplication  # noqa: E402
 
 
 app_instance = QApplication([])
 # If the following line is commented, application doesn't crash on exit anymore.
 app_instance2 = app_instance
-QTimer.singleShot(0, qApp.quit)
-app_instance.exec_()
+QTimer.singleShot(0, qApp.quit)  # noqa: F821
+app_instance.exec()
index 86e7c60ddc4cd8fd40a5dfa8f57d5f3e0ade09c4..dbcd77db93a737ccd8353ae25fbe112f1ca0756c 100644 (file)
@@ -20,11 +20,11 @@ class QAppPresence(unittest.TestCase):
 
     def testQApp(self):
         # QtGui.qApp variable is instance of QApplication
-        self.assertTrue(isinstance(qApp, QApplication))
+        self.assertTrue(isinstance(qApp, QApplication))  # noqa: F821
 
 
 def main():
-    app = QApplication([])
+    app = QApplication([])  # noqa: F841
     unittest.main()
 
 
index 6d36dfb2065e8e902c828b9d51b640eadbc0775e..c75f2eb8e74cc115d33e993bd52c443a380656ed 100644 (file)
@@ -13,15 +13,28 @@ from init_paths import init_test_paths
 init_test_paths(False)
 
 from PySide6.QtCore import Slot, QTimer
-from PySide6.QtWidgets import QDialog, QMainWindow
+from PySide6.QtWidgets import QApplication, QDialog, QMainWindow
 from helper.timedqapplication import TimedQApplication
 
 
+def is_exposed(widget):
+    result = False
+    if widget.isVisible():
+        handle = widget.windowHandle()
+        if handle:
+            result = handle.isExposed()
+    return result
+
+
 class Window(QMainWindow):
     def __init__(self):
         super().__init__()
         self.setWindowTitle("Main")
         self.dialog = None
+        self._timer = QTimer()
+        self._timer.setInterval(50)
+        self._timer.timeout.connect(self._timer_handler)
+        self._timer.start()
 
     @Slot()
     def execDialog(self):
@@ -33,6 +46,13 @@ class Window(QMainWindow):
         dialog.exec()
         self.close()
 
+    @Slot()
+    def _timer_handler(self):
+        """Periodically check for the dialog to appear and close it."""
+        for widget in QApplication.topLevelWidgets():
+            if isinstance(widget, QDialog) and is_exposed(widget):
+                widget.reject()
+
 
 class DialogExecTest(TimedQApplication):
     """Test whether the parent-child relationship (dialog/main window) is removed when
@@ -44,7 +64,9 @@ class DialogExecTest(TimedQApplication):
 
     def testExec(self):
         self._window.show()
-        QTimer.singleShot(500, self._window.execDialog)
+        while not is_exposed(self._window):
+            QApplication.processEvents()
+        QTimer.singleShot(0, self._window.execDialog)
         self.app.exec()
         self.assertTrue(self._window.dialog() is None)
 
index dd4209d1047c01761a1e7893ac90635bdd221ba9..d9868a9eb4a238ab6982d40dd06fb8a36177e976 100644 (file)
@@ -11,21 +11,48 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
 from init_paths import init_test_paths
 init_test_paths(False)
 
+from PySide6.QtCore import QTimer
 from PySide6.QtGui import QFont
-from PySide6.QtWidgets import QFontDialog
+from PySide6.QtWidgets import QApplication, QDialog, QFontDialog
 from helper.timedqapplication import TimedQApplication
 
 
+def is_exposed(widget):
+    result = False
+    if widget.isVisible():
+        handle = widget.windowHandle()
+        if handle:
+            result = handle.isExposed()
+    return result
+
+
 class TestFontDialog(TimedQApplication):
 
-    def testGetFont(self):
-        QFontDialog.getFont()
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._timer = None
+
+    def setUp(self, timeout=100):
+        super().setUp(timeout)
+        if not self._timer:
+            self._timer = QTimer()
+            self._timer.setInterval(50)
+            self._timer.timeout.connect(self._timer_handler)
+            self._timer.start()
 
-    def testGetFontQDialog(self):
-        QFontDialog.getFont(QFont("FreeSans", 10))
+    def _timer_handler(self):
+        """Periodically check for the dialog to appear and close it."""
+        for widget in QApplication.topLevelWidgets():
+            if isinstance(widget, QDialog) and is_exposed(widget):
+                widget.accept()
 
     def testGetFontQDialogQString(self):
-        QFontDialog.getFont(QFont("FreeSans", 10), None, "Select font")
+        r = QFontDialog.getFont(QFont("FreeSans", 10), None, "Select font",
+                                QFontDialog.FontDialogOption.DontUseNativeDialog)
+        self.assertTrue(type(r) is tuple)
+        self.assertEqual(len(r), 2)
+        self.assertTrue(r[0])
+        self.assertTrue(type(r[1]) is QFont)
 
 
 if __name__ == '__main__':
index 71aba9941e137342d9c9fa6dd5796b08b9da7ef4..7ae29d1f7e6f11472240da2922e8b32b5985c64a 100644 (file)
@@ -50,10 +50,19 @@ class QGraphicsObjectReimpl(UsesQApplication):
         #   and then the QVariant was not associated with
         #   a QGraphicsItem but a QObjectItem because the base
         #   class was a QObject.
+        #   See also PYSIDE-1887, PYSIDE-3069
         gobjA = GObjA()
         gobjA.setParentItem(w)
+        print(gobjA.parentItem())
         self.assertIs(type(w), type(gobjA.parentItem()))
 
+        # PYSIDE-3115: QVariant conversion of the parent
+        # (Python class inheriting QGraphicsObject).
+        parentA = GObjA()
+        gobjA = GObjA()
+        gobjA.setParentItem(parentA)
+        self.assertIs(type(parentA), type(gobjA.parentItem()))
+
         gobjB = GObjB()
         gobjB.setParentItem(w)
         self.assertIs(type(w), type(gobjB.parentItem()))
index fa6455f14d81a3fc064fd8a44050921eef5069e6..32ee4857ec64be3932f956b6588b3d21c23287c2 100644 (file)
@@ -16,29 +16,46 @@ from PySide6.QtWidgets import QApplication, QInputDialog, QDialog
 from helper.usesqapplication import UsesQApplication
 
 
-def close_dialog():
-    for w in QApplication.topLevelWidgets():
-        if isinstance(w, QDialog):
-            w.reject()
+def is_exposed(widget):
+    result = False
+    if widget.isVisible():
+        handle = widget.windowHandle()
+        if handle:
+            result = handle.isExposed()
+    return result
 
 
 class TestInputDialog(UsesQApplication):
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._timer = None
+
+    def setUp(self):
+        super().setUp()
+        if not self._timer:
+            self._timer = QTimer()
+            self._timer.setInterval(50)
+            self._timer.timeout.connect(self._timer_handler)
+            self._timer.start()
+
+    def _timer_handler(self):
+        """Periodically check for the dialog to appear and close it."""
+        for widget in QApplication.topLevelWidgets():
+            if isinstance(widget, QDialog) and is_exposed(widget):
+                widget.reject()
+
     def testGetDouble(self):
-        QTimer.singleShot(500, close_dialog)
         self.assertEqual(QInputDialog.getDouble(None, "title", "label"), (0.0, False))
 
     def testGetInt(self):
-        QTimer.singleShot(500, close_dialog)
         self.assertEqual(QInputDialog.getInt(None, "title", "label"), (0, False))
 
     def testGetItem(self):
-        QTimer.singleShot(500, close_dialog)
         (item, bool) = QInputDialog.getItem(None, "title", "label", ["1", "2", "3"])
         self.assertEqual(str(item), "1")
 
     def testGetText(self):
-        QTimer.singleShot(500, close_dialog)
         (text, bool) = QInputDialog.getText(None, "title", "label")
         self.assertEqual(str(text), "")
 
index 660addf4ec4e9ba562aaffbfd4f944c6fd63dd15..cf4939d0bd239cea902d46748704166ed44b5ecb 100644 (file)
@@ -137,35 +137,35 @@ class InternalAdd(UsesQApplication):
         topLayout = QGridLayout()
 
         # unique reference
-        self.assertEqual(sys.getrefcount(w), 2)
-        self.assertEqual(sys.getrefcount(ow), 2)
+        base_ref_count_w = sys.getrefcount(w)
+        base_ref_count_ow = sys.getrefcount(ow)
 
         topLayout.addWidget(w, 0, 0)
         topLayout.addWidget(ow, 1, 0)
 
         # layout keep the referemce
-        self.assertEqual(sys.getrefcount(w), 3)
-        self.assertEqual(sys.getrefcount(ow), 3)
+        self.assertEqual(sys.getrefcount(w), base_ref_count_w + 1)
+        self.assertEqual(sys.getrefcount(ow), base_ref_count_ow + 1)
 
         mainLayout = QGridLayout()
 
         mainLayout.addLayout(topLayout, 1, 0, 1, 4)
 
         # the same reference
-        self.assertEqual(sys.getrefcount(w), 3)
-        self.assertEqual(sys.getrefcount(ow), 3)
+        self.assertEqual(sys.getrefcount(w), base_ref_count_w + 1)
+        self.assertEqual(sys.getrefcount(ow), base_ref_count_ow + 1)
 
         mw.setLayout(mainLayout)
 
         # now trasfer the ownership to mw
-        self.assertEqual(sys.getrefcount(w), 3)
-        self.assertEqual(sys.getrefcount(ow), 3)
+        self.assertEqual(sys.getrefcount(w), base_ref_count_w + 1)
+        self.assertEqual(sys.getrefcount(ow), base_ref_count_ow + 1)
 
         del mw
 
         # remove the ref and invalidate the widget
-        self.assertEqual(sys.getrefcount(w), 2)
-        self.assertEqual(sys.getrefcount(ow), 2)
+        self.assertEqual(sys.getrefcount(w), base_ref_count_w)
+        self.assertEqual(sys.getrefcount(ow), base_ref_count_ow)
 
 
 if __name__ == '__main__':
index 26449c34636d8a4c8edf7fb9f7ea29fdbf235564..c12126b1c58842b817e194a8fde63c3d3eaf0ab1 100644 (file)
@@ -69,39 +69,41 @@ class QLayoutTest(UsesQApplication):
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testOwnershipTransfer(self):
         b = QPushButton("teste")
+        base_ref_count = sys.getrefcount(b)
         layout = MyLayout()
 
         layout.addWidget(b)
 
-        self.assertEqual(sys.getrefcount(b), 2)
+        self.assertEqual(sys.getrefcount(b), base_ref_count)
 
         w = QWidget()
 
         # transfer ref
         w.setLayout(layout)
 
-        self.assertEqual(sys.getrefcount(b), 3)
+        self.assertEqual(sys.getrefcount(b), base_ref_count + 1)
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testReferenceTransfer(self):
         b = QPushButton("teste")
+        base_ref_count = sys.getrefcount(b)
         layout = QHBoxLayout()
 
         # keep ref
         layout.addWidget(b)
-        self.assertEqual(sys.getrefcount(b), 3)
+        self.assertEqual(sys.getrefcount(b), base_ref_count + 1)
 
         w = QWidget()
 
         # transfer ref
         w.setLayout(layout)
 
-        self.assertEqual(sys.getrefcount(b), 3)
+        self.assertEqual(sys.getrefcount(b), base_ref_count + 1)
 
         # release ref
         del w
 
-        self.assertEqual(sys.getrefcount(b), 2)
+        self.assertEqual(sys.getrefcount(b), base_ref_count)
 
     def testMissingFunctions(self):
         w = QWidget()
index 9bfc81d652960bc39a68b2032b2dcf32641c316f..0608b708afae2571e3c1483cde312c0f8c0676cb 100644 (file)
@@ -36,7 +36,7 @@ class QListWidgetItemConstructor(UsesQApplication):
 
     def testConstructorWithNone(self):
         # Bug 452 - QListWidgetItem() not casting NoneType to null correctly.
-        item = QListWidgetItem(None, 123)
+        item = QListWidgetItem(None, 123)  # noqa: F841
 
 
 if __name__ == '__main__':
index 7d73aca574e8dc18021a66d11c9ade24bacc6e55..0e71b35d02151a78fc3c66507b8012f18c7a51c6 100644 (file)
@@ -12,7 +12,6 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
 from init_paths import init_test_paths
 init_test_paths(False)
 
-from PySide6.QtCore import QTimer
 from PySide6.QtWidgets import QMainWindow, QPushButton, QToolButton, QWidget
 from helper.usesqapplication import UsesQApplication
 
@@ -40,12 +39,6 @@ class MyButton(QPushButton):
 
 class TestMainWindow(UsesQApplication):
 
-    def testCreateToolbar(self):
-        w = MainWindow()
-        w.show()
-        QTimer.singleShot(1000, self.app.quit)
-        self.app.exec()
-
     def objDel(self, obj):
         self.app.quit()
 
@@ -53,9 +46,9 @@ class TestMainWindow(UsesQApplication):
     def testRefCountToNull(self):
         w = QMainWindow()
         c = QWidget()
-        self.assertEqual(sys.getrefcount(c), 2)
+        base_ref_count = sys.getrefcount(c)
         w.setCentralWidget(c)
-        self.assertEqual(sys.getrefcount(c), 3)
+        self.assertEqual(sys.getrefcount(c), base_ref_count + 1)
         wr = weakref.ref(c, self.objDel)  # noqa: F841
         w.setCentralWidget(None)
         c = None
@@ -65,13 +58,14 @@ class TestMainWindow(UsesQApplication):
     def testRefCountToAnother(self):
         w = QMainWindow()
         c = QWidget()
-        self.assertEqual(sys.getrefcount(c), 2)
+        base_ref_count = sys.getrefcount(c)
         w.setCentralWidget(c)
-        self.assertEqual(sys.getrefcount(c), 3)
+        self.assertEqual(sys.getrefcount(c), base_ref_count + 1)
 
         c2 = QWidget()
+        base_ref_count = sys.getrefcount(c2)
         w.setCentralWidget(c2)
-        self.assertEqual(sys.getrefcount(c2), 3)
+        self.assertEqual(sys.getrefcount(c2), base_ref_count + 1)
 
         wr = weakref.ref(c, self.objDel)  # noqa: F841
         w.setCentralWidget(None)
index 7d1d262e4352cb467f482b60b617339be2e24f25..8bd5d1624540c2e26cb32e57e9f2d8c6ce650bfd 100644 (file)
@@ -33,16 +33,16 @@ class QMenuAddAction(UsesQApplication):
 
     def testAddActionWithoutKeySequenceCallable(self):
         # bug #280
-        action = self.menu.addAction(self.app.tr('aaa'), lambda: 1)
+        action = self.menu.addAction(self.app.tr('aaa'), lambda: 1)  # noqa: F841
 
     def testAddActionKeySequenceCallable(self):
         # bug #228
-        action = self.menu.addAction(self.app.tr('aaa'), lambda: 1,
+        action = self.menu.addAction(self.app.tr('aaa'), lambda: 1,  # noqa: F841
                                      QKeySequence(self.app.tr('Ctrl+O')))
 
     def testAddActionKeySequenceSlot(self):
-        action = self.menu.addAction('Quit', self.app, SLOT('quit()'),
-                                     QKeySequence('Ctrl+O'))
+        action = self.menu.addAction('Quit', QKeySequence('Ctrl+O'),  # noqa: F841
+                                     self.app, SLOT('quit()'))
 
 
 class QMenuAddActionWithIcon(UsesQApplication):
@@ -61,16 +61,16 @@ class QMenuAddActionWithIcon(UsesQApplication):
 
     def testAddActionWithoutKeySequenceCallable(self):
         # bug #280
-        action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1)
+        action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1)  # noqa: F841
 
     def testAddActionKeySequenceCallable(self):
         # bug #228
-        action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1,
-                                     QKeySequence(self.app.tr('Ctrl+O')))
+        action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1,  # noqa: F841
+                                     QKeySequence(self.app.tr('Ctrl+O')))  # noqa: F841
 
     def testAddActionKeySequenceSlot(self):
-        action = self.menu.addAction(self.icon, 'Quit', self.app, SLOT('quit()'),
-                                     QKeySequence('Ctrl+O'))
+        action = self.menu.addAction(self.icon, 'Quit', QKeySequence('Ctrl+O'),  # noqa: F841
+                                     self.app, SLOT('quit()'))  # noqa: F841
 
 
 if __name__ == '__main__':
index 884f391a9b43ecb877aed47e4ebbb911b38c2eb5..e9b0440c29895c0958623e718576233c3d0d940b 100644 (file)
@@ -12,16 +12,20 @@ from init_paths import init_test_paths
 init_test_paths(False)
 
 from helper.usesqapplication import UsesQApplication
-from PySide6.QtCore import QTimer
+from PySide6.QtCore import QCoreApplication, QTimer
 from PySide6.QtGui import QPicture, QPainter
 from PySide6.QtWidgets import QWidget
 
 
 class MyWidget(QWidget):
+    def __init__(self, picture):
+        super().__init__()
+        self._picture = picture
+
     def paintEvent(self, e):
         with QPainter(self) as p:
             p.drawPicture(0, 0, self._picture)
-        self._app.quit()
+        QTimer.singleShot(0, qApp.quit)  # noqa: F821
 
 
 class QPictureTest(UsesQApplication):
@@ -36,11 +40,11 @@ class QPictureTest(UsesQApplication):
 
         self.assertEqual(picture2.data(), picture.data())
 
-        w = MyWidget()
-        w._picture = picture2
-        w._app = self.app
+        w = MyWidget(picture2)
 
-        QTimer.singleShot(300, w.show)
+        w.show()
+        while not w.windowHandle().isExposed():
+            QCoreApplication.processEvents()
         self.app.exec()
 
 
index a6970cf4385cb27769cd29adee5f3592a2d336b7..0cb59016948566b24ac0ea0c328b84a6e3c93e07 100644 (file)
@@ -65,9 +65,9 @@ class SetStyleTest(UsesQApplication):
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testSetStyleOwnership(self):
         style = QStyleFactory.create(QStyleFactory.keys()[0])
-        self.assertEqual(sys.getrefcount(style), 2)
+        base_ref_count = sys.getrefcount(style)
         QApplication.instance().setStyle(style)
-        self.assertEqual(sys.getrefcount(style), 3)
+        self.assertEqual(sys.getrefcount(style), base_ref_count + 1)
 
 
 if __name__ == '__main__':
index 9cfc526c1bb1a8b7155a2fa2c19576f58adefba7..a8d80d6f0fd2dcb3be5918fe98956f3be734941d 100644 (file)
@@ -39,7 +39,7 @@ class DontTouchReference(UsesQApplication):
         # C++ object already deleted." when accessing the QTextTable
         del self.cursor
         self.assertEqual(sys.getrefcount(self.table), 2)
-        cell = self.table.cellAt(0, 0)
+        cell = self.table.cellAt(0, 0)  # noqa: F841
 
 
 if __name__ == "__main__":
index 1ee8c45cb9f7938a52f33f0e635233b2f928c8d2..06ce4421e92e2ba77c96b82dd91de5a408bfb272 100644 (file)
@@ -32,7 +32,7 @@ class QTreeWidgetTest(UsesQApplication):
         treeWidget.insertTopLevelItem(0, item)
         # Adding QPushButton inside the method
         treeWidget.setItemWidget(item, 1,
-            QPushButton('Push button on column 1'))
+                                 QPushButton('Push button on column 1'))
 
         # Getting the widget back
         w = treeWidget.itemWidget(treeWidget.itemAt(0, 1), 1)
index 4636aba9991a8ed1005b2ff4e22bb98d1bba5690..9a82f2c93442cf8c6f4d2b500801181a03e36784 100644 (file)
@@ -8,8 +8,8 @@ import sys
 SRC_DIR = os.path.dirname(os.path.abspath(__file__))
 sys.path.append(os.path.join(os.path.dirname(os.path.dirname(SRC_DIR)),
                              'shiboken6', 'tests'))
-from shiboken_paths import (get_dir_env_var, get_build_dir, add_python_dirs,
-                            add_lib_dirs, shiboken_paths)
+from shiboken_paths import (get_dir_env_var, get_build_dir, add_python_dirs,  # noqa: E402
+                            add_lib_dirs, shiboken_paths)  # noqa: E402
 
 
 def _get_qt_dir():
index 4ef396fe600c886444c7df021fa5a87819348149..2e1b2db33c4c08bfe62f3be873369d63182acc47 100644 (file)
@@ -16,8 +16,14 @@ No argument: Use a default Python for each platform (author specific).
     --python <python>   use that specific python interpreter
     --dry-run           try it first without compilation
     --pip               automatically install the needed modules
+    --absolute          compare against the status from
+                        b887919ea244a057f15be9c1cdc652538e3fe9a0
+                        Yocto: allow LLVM 14 for building PySide
+                        2025-01-23 18:18
+    --limited-api yes|no default=yes
 """
 import argparse
+import getpass
 import os
 import platform
 import re
@@ -27,12 +33,24 @@ import sys
 from ast import literal_eval
 from pathlib import Path
 
-defaults = {
-    "Darwin": "/Users/tismer/.pyenv/versions/3.12.5/bin/python3",
-    "Windows": "d:/py312_64/python.exe",
-    "Linux": "/home/ctismer/.pyenv/versions/3.12.5/bin/python3",
+
+defaults = {    # Python, extras
+    "Darwin":   ("/Users/tismer/.pyenv/versions/3.12.5/bin/python3", []),   # noqa: E241
+    "Windows":  ("d:/py312_64/python.exe", []),                             # noqa: E241
+    "Linux":    ("/home/ctismer/.pyenv/versions/3.12.5/bin/python3", [])    # noqa: E241
+}
+
+reference = {   # Limited API no / yes
+    "Darwin":   (26165741, 26078531),   # noqa: E241
+    "Windows":  (15324160, 15631872),   # noqa: E241
+    "Linux":    (19203176, 19321976),   # noqa: E241
 }
 
+if "tismer" not in getpass.getuser():
+    # assume a colleague.
+    defaults["Linux"] = "python", ["--qt-src-dir", "/~/qt-69/qt-69/qtbase"]
+    # defaults["Windows"] = "...", [...]    # please insert your defaults
+
 
 def setup_project_dir():
     look_for = Path("testing")
@@ -40,7 +58,7 @@ def setup_project_dir():
     while here / look_for not in here.iterdir():
         parent = here.parent
         if parent == here:
-            raise SystemError(look_for + " not found!")
+            raise SystemError(f"{look_for} not found!")
         here = parent
     fsp = os.fspath(here)
     if fsp not in sys.path:
@@ -58,7 +76,7 @@ def check_allowed_python_versions(major, minor):
     from build_scripts.main import config
     pattern = r'Programming Language :: Python :: (\d+)\.(\d+)'
     hist = []
-    for line in config.python_version_classifiers:
+    for line in config.classifiers:
         found = re.search(pattern, line)
         if found:
             ma = int(found.group(1))
@@ -90,12 +108,6 @@ def get_result_size(build_dir):
 
 setup_project_dir()
 plat = platform.system()
-options = [
-    "setup.py", "build", "--limited-api=no", "--skip-docs", "--no-qt-tools",
-    "--module-subset=Core,Gui,Widgets"]
-
-options_base = options + ["--unoptimize=all"]
-options_best = options
 
 if __name__ == "__main__":
     parser = argparse.ArgumentParser()
@@ -103,9 +115,13 @@ if __name__ == "__main__":
     parser.add_argument("--dry-run", "-d", action="store_true")
     parser.add_argument("--pip", action="store_true", help="""
         Install the necessary modules automatically, which can save some trouble""")
-    args = parser.parse_args()
+    parser.add_argument("--absolute", "-a", action="store_true", help="""
+        Measure against the state on 2025-01-23""")
+    parser.add_argument("--limited-api", "-l", choices=["yes", "no"], default="yes", help="""
+        Use of limited API. Recommended because this is the CI default""")
 
-    python = args.python or defaults[plat] if plat in defaults else args.python
+    args = parser.parse_args()
+    python = args.python or defaults[plat][0] if plat in defaults else args.python
     python = Path(python).expanduser()
 
     if not python.exists:
@@ -125,13 +141,24 @@ if __name__ == "__main__":
         subprocess.run([python, "-m", "pip", "uninstall", "-y"] + needs_imports)
         subprocess.run([python, "-m", "pip", "install"] + needs_imports)
 
+    options = [
+        "setup.py", "build", "--limited-api=" + args.limited_api, "--skip-docs",
+        "--log-level", "quiet", "--unity", "--no-qt-tools",
+        "--module-subset=Core,Gui,Widgets"] + defaults[plat][1]
+    options_base = options + ["--unoptimize=all"]
+    options_best = options
+
+    use_limited_api = args.limited_api == "yes"
     skip = args.dry_run
-    cmd = [python] + options_base
-    if not skip:
-        subprocess.run(cmd)
+    if args.absolute:
+        res_base = reference[plat][use_limited_api]
+    else:
+        cmd = [python] + options_base
+        if not skip:
+            subprocess.run(cmd)
 
-    build_dir = get_build_dir()
-    res_base = get_result_size(build_dir)
+        build_dir = get_build_dir()
+        res_base = get_result_size(build_dir)
 
     cmd = [python] + options_best
     if not skip:
@@ -140,10 +167,11 @@ if __name__ == "__main__":
     build_dir = get_build_dir()
     res_best = get_result_size(build_dir)
 
+    add_text = " on 2025-01-27" if args.absolute else ""
     print()
     print(f"Compiling with {python}")
-    print(f"Platform = {plat}")
-    print(f"base size = {res_base}")
+    print(f"Platform = {plat}   limited_api = {args.limited_api}")
+    print(f"base size = {res_base}{add_text}")
     print(f"best size = {res_best}")
     print(f"improvement {(res_base - res_best) / res_base:%}")
     if skip:
index 8b4de5d8e91f51630be317eb77b45aec14551f11..4a7e2e1d1de4a811e39269b18c59e2b797b8701f 100644 (file)
@@ -156,6 +156,7 @@ PYSIDE_TEST(new_inherited_functions_test.py)
 PYSIDE_TEST(notify_id.py)
 PYSIDE_TEST(properties_test.py)
 PYSIDE_TEST(property_python_test.py)
+PYSIDE_TEST(snake_case_sub.py)
 PYSIDE_TEST(snake_case_test.py)
 PYSIDE_TEST(true_property_test.py)
 PYSIDE_TEST(qapp_like_a_macro_test.py)
index 2ec2d56ee4ee387212ee928b84e9cee99a729976..90754c88709972c8c9a43ddfc89e133174d2f633 100644 (file)
@@ -11,7 +11,6 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
 from init_paths import init_test_paths
 init_test_paths(True)
 
-import shiboken6
 from testbinding import getHiddenObject
 
 
index cfbe3ac724f8ea4a3fed1494e65894c2ecc065a3..ab20cbab456c00f1ab8a1acc308121f3a0956635 100644 (file)
@@ -49,8 +49,12 @@ class ListConnectionTest(unittest.TestCase):
 
 
 # PYSIDE-1735: We are testing that opcodes do what they are supposed to do.
-#              This is needed in the PyEnum forgiveness mode where we need
-#              to introspect the code if an Enum was called with no args.
+#              This is needed in the PyEnum forgiveness mode (ENOPT_NO_ZERODEFAULT)
+#              where we need to introspect the code if an Enum was called with no args,
+#              enabling default construction like 'f = Qt.WindowFlags()'.
+#              Adapt for each Python version by checking the defines in the generated header opcode_ids.h
+# egrep '( RESUME | LOAD_GLOBAL | LOAD_ATTR | PUSH_NULL | CALL | STORE_FAST | RETURN_CONST )' opcode_ids.h
+# See also sbkfeature_base.cpp
 
 # flake8: noqa
 class InvestigateOpcodesTest(unittest.TestCase):
@@ -75,7 +79,6 @@ class InvestigateOpcodesTest(unittest.TestCase):
 
     _sin = sys.implementation.name
 
-    @unittest.skipIf(hasattr(sys.flags, "nogil"), f"{_sin} has different opcodes")
     def testByteCode(self):
         import dis
         # opname, opcode, arg
@@ -171,7 +174,7 @@ class InvestigateOpcodesTest(unittest.TestCase):
                         ('STORE_FAST',   125, 1),
                         ('RETURN_CONST', 121, 0)]
 
-        if sys.version_info[:2] >= (3, 13):
+        if sys.version_info[:2] == (3, 13):
 
             result_1 = [('RESUME',       149, 0),
                         ('LOAD_GLOBAL',   91, 0),
@@ -187,6 +190,25 @@ class InvestigateOpcodesTest(unittest.TestCase):
                         ('STORE_FAST',   110, 1),
                         ('RETURN_CONST', 103, 0)]
 
+        if sys.version_info[:2] >= (3, 14):
+
+            result_1 = [('RESUME',       128, 0),
+                        ('LOAD_GLOBAL',   92, 0),
+                        ('LOAD_ATTR',     80, 2),
+                        ('STORE_FAST',   112, 1),
+                        ('LOAD_CONST',    82, 0),
+                        ('RETURN_VALUE',  35, None)
+                        ]
+
+            result_2 = [('RESUME',       128, 0),
+                        ('LOAD_GLOBAL',   92, 0),
+                        ('LOAD_ATTR',     80, 2),
+                        ('PUSH_NULL',     33, None),
+                        ('CALL',          52, 0),
+                        ('STORE_FAST',   112, 1),
+                        ('LOAD_CONST',    82, 0),
+                        ('RETURN_VALUE',  35, None)]
+
         self.assertEqual(self.read_code(self.probe_function1), result_1)
         self.assertEqual(self.read_code(self.probe_function2), result_2)
 
index 1a54f1848e5cf531022e92fd815a9b28de3c32d4..dd674646a65f46df87afe0b820bd2de81c8327a8 100644 (file)
@@ -39,10 +39,10 @@ class PySequenceTest(unittest.TestCase):
         testfunc = QtCore.QUrl.fromStringList
         # use a generator (iterable)
         self.assertEqual(testfunc(gen(["asd", "ghj"])),
-            [PySide6.QtCore.QUrl('asd'), PySide6.QtCore.QUrl('ghj')])
+                         [PySide6.QtCore.QUrl('asd'), PySide6.QtCore.QUrl('ghj')])
         # use an iterator
         self.assertEqual(testfunc(iter(["asd", "ghj"])),
-            [PySide6.QtCore.QUrl('asd'), PySide6.QtCore.QUrl('ghj')])
+                         [PySide6.QtCore.QUrl('asd'), PySide6.QtCore.QUrl('ghj')])
         self.assertRaises(IndexError, testfunc, gen(["asd", "crash", "ghj"]))
         # testing QMatrix4x4
         testfunc = QtGui.QMatrix4x4
index fd253095be205c51374850fa6bcd13360275ee33..83d80efca709eeff6452a8ac59dc3325ecd13f0e 100644 (file)
@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 import os
+import re
 import sys
 import unittest
 import subprocess
@@ -28,6 +29,21 @@ is_ci = qtest_env == "ci"
 USE_MYPY = True if is_ci else HAVE_MYPY
 
 
+def dump_erroneous_pyi_files(err_lines, pyi_dir):
+    seen = set()
+    for err_line in err_lines:
+        if match := re.search(r"Qt\w+\.pyi", err_line):
+            if (pyi := match.group(0)) not in seen:
+                seen.add(pyi)
+                print(f"----- dump of {pyi} -----")
+                with open(Path(pyi_dir) / pyi) as f:
+                    line_no = 0
+                    for line in f:
+                        line_no += 1
+                        print(f"{pyi}:{line_no} {line.rstrip()}")
+                print()
+
+
 @unittest.skipIf(not USE_MYPY, "The mypy test was skipped because mypy is not installed")
 @unittest.skipIf(SKIP_MYPY_TEST, "The mypy test was disabled")
 class MypyCorrectnessTest(unittest.TestCase):
@@ -53,14 +69,17 @@ class MypyCorrectnessTest(unittest.TestCase):
     def testMypy(self):
         self.assertTrue(HAVE_MYPY)
         insert_version = ["--python-version", "3.11"] if sys.version_info[:2] < (3, 11) else []
-        cmd = ([sys.executable, "-m", "mypy", "--cache-dir", self.cache_dir]
+        cmd = ([sys.executable, "-m", "mypy", "--pretty", "--cache-dir", self.cache_dir]
                + insert_version + [self.pyside_dir])
         time_pre = time.time()
         ret = subprocess.run(cmd, capture_output=True)
         time_post = time.time()
-        for line in ret.stdout.decode("utf-8").split("\n"):
+        err_lines = ret.stdout.decode("utf-8").split("\n")
+        for line in err_lines:
             print(line)
         print(f"Time used for mypy test = {(time_post - time_pre):.5} s")
+        if ret.returncode != 0 and os.environ.get("QTEST_ENVIRONMENT", "") == "ci":
+            dump_erroneous_pyi_files(err_lines, self.pyside_dir)
         self.assertEqual(ret.returncode, 0)
 
 
index 49ba6969f09ba3db25c57d37a1b70ba71ce665d9..43c7b0dff049789e73889a19f209ab872fe42a46 100644 (file)
@@ -9,10 +9,10 @@ import unittest
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-from PySide6.QtCore import QObject, Signal, Property, Slot
+from PySide6.QtCore import QObject, Signal, Property, Slot  # noqa: E402
 
 '''Tests that the signal notify id of a property is correct, aka corresponds to the initially set
 notify method.'''
index 06ee338fd34dfc0204f3c80443588e0ea1d621a5..e087333c4e83ad52205a8dac1158ad5cba8ab3a2 100644 (file)
@@ -13,10 +13,10 @@ import warnings
 
 from pathlib import Path
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths  # noqa: E402
 init_test_paths(False)
 
-import PySide6.QtCore as QtCore
+import PySide6.QtCore as QtCore  # noqa: E402
 
 
 class Whatever(QtCore.QObject):
index 2253572b042f1ec96edff859e375f15d57f5e53a..e9be966c3b76eda0361f3c2dc02b924ccb285193 100644 (file)
@@ -34,7 +34,7 @@ def emit_upon_success(signal):
     def f_(f):
         def f__(self):
             result = f(self)
-            s = signal.__get__(self)
+            s = signal.__get__(self)  # noqa: F841
             print(result)
             return result
         return f__
index 34b8f7f62038b79dafe80eabf28c518c3b22477a..64147e15ed8235b9da3aee8b2ed715b5a8495d88 100644 (file)
@@ -27,6 +27,11 @@ class Receiver(QObject):
     def __init__(self, p=None):
         super().__init__(p)
         self.received_handle = -1
+        self.received_name = ''
+
+    @Slot(QObject)
+    def slotObjectByConstRef(self, o):
+        self.received_name = o.objectName()
 
     @Slot(Connection)
     def connectionSlot(self, c):
@@ -37,7 +42,9 @@ class SignalEmissionFromPython(unittest.TestCase):
 
     def setUp(self):
         self.obj1 = TestObject(0)
+        self.obj1.setObjectName('obj1')
         self.obj2 = TestObject(0)
+        self.obj2.setObjectName('obj2')
         self.one_called = 0
         self.two_called = 0
 
@@ -114,6 +121,12 @@ class SignalEmissionFromPython(unittest.TestCase):
         self.assertEqual(self.one_called, 1)
         self.assertEqual(self.two_called, 2)
 
+    def testSignalObjectByConstRef(self):
+        receiver = Receiver()
+        self.obj1.objectByConstRef.connect(receiver.slotObjectByConstRef)
+        self.obj1.emitObjectByConstRef()
+        self.assertEqual(receiver.received_name, 'obj1')
+
 
 if __name__ == '__main__':
     unittest.main()
index bcf629bf837a85651e6e73faa352b3ef3b06b609..3107be09a01fdd53019c590a529f75feb86b71e7 100644 (file)
@@ -47,6 +47,11 @@ void TestObject::emitFlagsSignal(Qt::Alignment alignment)
     emit flagsSignal(alignment);
 }
 
+void TestObject::emitObjectByConstRef()
+{
+    emit objectByConstRef(*this);
+}
+
 void TestObject::setQLatin1String(QLatin1String v)
 {
     m_qLatin1String = v;
index 9689589ad3e9b8bf9b77837a52af0f9cc50fa3f8..7c273c89164f84cb7efb3ec26a186c6ab24031e0 100644 (file)
@@ -54,6 +54,8 @@ public:
 
     void emitFlagsSignal(Qt::Alignment alignment);
 
+    void emitObjectByConstRef();
+
     static constexpr auto LATIN1_TEST_FIELD = QLatin1StringView("test");
 
     void setQLatin1String(QLatin1String v);
@@ -69,6 +71,7 @@ signals:
     void signalWithTypedefValue(TypedefValue value);
     void signalWithContainerTypedefValue(const IntList &il);
     void flagsSignal(Qt::Alignment alignment);
+    void objectByConstRef(const QObject &qobject);
 
 private:
     int m_idValue;
index 2ad4c5b1bd381c067279c4f8b05119efeaa4e5ce..161cea19dde2bd5628cf482943f8ebc942a76f36 100644 (file)
@@ -44,7 +44,7 @@ from init_platform import enum_all, generate_all
 from util import (isolate_warnings, check_warnings, suppress_warnings, warn,
                   is_ci, qt_version, get_script_dir, get_effective_refpath,
                   get_refpath, import_refmodule)
-from PySide6 import *
+from PySide6 import *  # noqa
 
 refPath = get_refpath()
 effectiveRefPath = get_effective_refpath()
@@ -55,7 +55,7 @@ if os.path.exists(pyc) and not os.path.exists(effectiveRefPath):
 
 if refPath != effectiveRefPath:
     print("*** Falling back to ", effectiveRefPath, " since expected ",
-        refPath, " does not exist")
+          refPath, " does not exist")
 
 script_dir = get_script_dir()
 shortpath = os.path.relpath(effectiveRefPath, script_dir)
@@ -92,7 +92,7 @@ class TestUnrecognizedOffending(unittest.TestCase):
     """
     def test_signatures_recognized(self):
         with isolate_warnings():
-            found_sigs = enum_all()
+            found_sigs = enum_all()  # noqa: F841
             if check_warnings():
                 raise RuntimeError("There are errors, see above.")
 
@@ -126,8 +126,8 @@ class TestSignaturesExists(unittest.TestCase):
             if key.startswith("sample.SampleNamespace"):
                 # We cannot work with sample namespaces after the change to __qualname__.
                 continue
-            if (key.startswith("smart.SharedPtr") or
-                re.match(r"PySide6\..*?\.QSharedPointer_", key)):
+            if (key.startswith("smart.SharedPtr")
+                    or re.match(r"PySide6\..*?\.QSharedPointer_", key)):
                 # These mangled names are not supported.
                 # We should fix them.
                 continue
@@ -135,9 +135,9 @@ class TestSignaturesExists(unittest.TestCase):
                 warn(f"missing key: '{key} value={value}'", stacklevel=3)
             else:
                 found_val = found_sigs[key]
-                if type(value) is list and (
-                        type(found_val) is tuple or
-                        len(found_val) < len(value)):
+                if type(value) is list and (  # noqa: W504
+                        type(found_val) is tuple
+                        or len(found_val) < len(value)):
                     # We check that nothing got lost. But it is ok when an older
                     # registry file does not know all variants, yet!
                     warn(multi_signature_msg(key, found_val, value), stacklevel=3)
index ff342adc73e85a0dd421fb325f9ae4aea058d2b6..094caaaf6286d3e864cbbd717435015df8a31573 100644 (file)
@@ -1,6 +1,7 @@
 # Copyright (C) 2023 The Qt Company Ltd.
 # SPDX-License-Identifier: BSD-3-Clause
 
+PYSIDE_TEST(anonymous_slot_leak_test.py)
 PYSIDE_TEST(args_dont_match_test.py)
 PYSIDE_TEST(bug_79.py)
 PYSIDE_TEST(bug_189.py)
@@ -20,6 +21,7 @@ PYSIDE_TEST(qobject_callable_connect_test.py)
 PYSIDE_TEST(qobject_destroyed_test.py)
 PYSIDE_TEST(qobject_receivers_test.py)
 PYSIDE_TEST(qobject_sender_test.py)
+PYSIDE_TEST(nonqobject_receivers_test.py)
 PYSIDE_TEST(ref01_test.py)
 PYSIDE_TEST(ref02_test.py)
 PYSIDE_TEST(ref03_test.py)
index 61ce4d5b850d29da5f0a2dea18caf722f533fa1b..29ef312b34fd3e4fc504de4a1e93cadb475e7348 100644 (file)
@@ -11,14 +11,24 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
 from init_paths import init_test_paths
 init_test_paths(True)
 
-from PySide6.QtCore import QObject, Signal
+from PySide6.QtCore import QObject, Signal, Slot, SIGNAL, SLOT
 from testbinding import TestObject
 
 
-class Foo(QObject):
+class Sender(QObject):
     bar = Signal()
 
 
+class Receiver(QObject):
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self.called = 0
+
+    @Slot()
+    def receiver(self):
+        self.called += 1
+
+
 class TestDisconnect(unittest.TestCase):
     def theSlot1(self):
         self.called1 = True
@@ -29,20 +39,81 @@ class TestDisconnect(unittest.TestCase):
     def testIt(self):
         self.called1 = False
         self.called2 = False
-        f = Foo()
-        f.bar.connect(self.theSlot1)
-        f.bar.connect(self.theSlot2)
-        f.bar.emit()
+        s = Sender()
+        s.bar.connect(self.theSlot1)
+        s.bar.connect(self.theSlot2)
+        s.bar.emit()
         self.assertTrue(self.called1)
         self.assertTrue(self.called2)
 
         self.called1 = False
         self.called2 = False
-        f.bar.disconnect()
-        f.bar.emit()
+        self.assertTrue(s.bar.disconnect())  # Disconnect sender
+        s.bar.emit()
         self.assertFalse(self.called1)
         self.assertFalse(self.called2)
 
+    def testCallable(self):
+        s = Sender()
+        r = Receiver()
+        s.bar.connect(r.receiver)
+        s.bar.emit()
+        self.assertEqual(r.called, 1)
+        self.assertTrue(s.bar.disconnect(r.receiver))
+        s.bar.emit()
+        self.assertEqual(r.called, 1)
+
+    def testStringBased(self):
+        s = Sender()
+        r = Receiver()
+        QObject.connect(s, SIGNAL("bar()"), r, SLOT("receiver()"))
+        s.bar.emit()
+        self.assertEqual(r.called, 1)
+        self.assertTrue(QObject.disconnect(s, SIGNAL("bar()"), r, SLOT("receiver()")))
+        s.bar.emit()
+        self.assertEqual(r.called, 1)
+
+    def testMixStringBasedCallable(self):
+        """PYSIDE-3020, Disconnect a string-based connection by passing a callable."""
+        s = Sender()
+        r = Receiver()
+        QObject.connect(s, SIGNAL("bar()"), r, SLOT("receiver()"))
+        s.bar.emit()
+        self.assertEqual(r.called, 1)
+        self.assertTrue(s.bar.disconnect(r.receiver))
+        s.bar.emit()
+        self.assertEqual(r.called, 1)
+
+    def testMixCallableStringBased(self):
+        """PYSIDE-3020, test vice versa."""
+        s = Sender()
+        r = Receiver()
+        s.bar.connect(r.receiver)
+        s.bar.emit()
+        self.assertEqual(r.called, 1)
+        self.assertTrue(QObject.disconnect(s, SIGNAL("bar()"), r, SLOT("receiver()")))
+        s.bar.emit()
+        self.assertEqual(r.called, 1)
+
+    def testMetaMethod(self):
+        s = Sender()
+        r = Receiver()
+        sender_mo = s.metaObject()
+        signal_index = sender_mo.indexOfMethod("bar()")
+        self.assertTrue(signal_index != -1)
+        signal_method = sender_mo.method(signal_index)
+        receiver_mo = r.metaObject()
+        slot_index = receiver_mo.indexOfMethod("receiver()")
+        self.assertTrue(slot_index != -1)
+        slot_method = receiver_mo.method(slot_index)
+        conn_id = QObject.connect(s, signal_method, r, slot_method)
+        self.assertTrue(conn_id)
+        s.bar.emit()
+        self.assertEqual(r.called, 1)
+        self.assertTrue(QObject.disconnect(s, signal_method, r, slot_method))
+        s.bar.emit()
+        self.assertEqual(r.called, 1)
+
     def testDuringCallback(self):
         """ Test to see if the C++ object for a connection is accessed after the
         method returns.  This causes a segfault if the memory that was used by the
diff --git a/sources/pyside6/tests/signals/nonqobject_receivers_test.py b/sources/pyside6/tests/signals/nonqobject_receivers_test.py
new file mode 100644 (file)
index 0000000..4bb47e5
--- /dev/null
@@ -0,0 +1,71 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+'''Test case for non-QObject.receivers'''
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths  # noqa: F401
+init_test_paths(False)
+
+from helper.usesqapplication import UsesQApplication  # noqa: F401
+
+from PySide6.QtGui import QAction  # noqa: F401
+
+
+receiver_instances = 0
+
+
+class Receiver:
+
+    def __init__(self):
+        global receiver_instances
+        receiver_instances += 1
+        self.slot1Triggered = 0
+        self.slot2Triggered = 0
+
+    def __del__(self):
+        global receiver_instances
+        receiver_instances -= 1
+
+    def slot1(self):
+        self.slot1Triggered += 1
+
+    def slot2(self):
+        self.slot2Triggered += 1
+
+
+class TestQObjectReceivers(UsesQApplication):
+    '''Test case for non-QObject.receivers'''
+
+    @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
+    def testBasic(self):
+        '''The test verifies that connections to methods of a non-QObject work
+           (TrackingMethodDynamicSlot). Also, despite being stored in the signal manager,
+           making connections should not increase references on the receiver or prevent
+           the receivers from being deleted, which is achieved using weak reference
+           tracking notifications.
+           2 connections are used to trigger a corruption caused by multiple weak reference
+           notifications (PYSIDE-3148).'''
+        action1 = QAction("a1", qApp)  # noqa: F821
+        action2 = QAction("a2", qApp)  # noqa: F821
+        receiver = Receiver()
+        self.assertEqual(receiver_instances, 1)
+        base_ref_count = sys.getrefcount(receiver)
+        action1.triggered.connect(receiver.slot1)
+        action2.triggered.connect(receiver.slot2)
+        self.assertEqual(sys.getrefcount(receiver), base_ref_count)
+        action1.trigger()
+        action2.trigger()
+        self.assertEqual(receiver.slot1Triggered, 1)
+        self.assertEqual(receiver.slot2Triggered, 1)
+        receiver = 0
+        self.assertEqual(receiver_instances, 0)
+
+
+if __name__ == '__main__':
+    unittest.main()
index ef2f5508249b1ed11face943ea64695c54562fcb..ad5542d4b9cd4eea85e7f342608172ff3d6b4729 100644 (file)
@@ -32,11 +32,11 @@ class DisconnectSignalsTest(unittest.TestCase):
         def destroyedSlot():
             pass
 
-        self.assertEqual(sys.getrefcount(destroyedSlot), 2)
+        base_ref_count = sys.getrefcount(destroyedSlot)
         self.emitter.destroyed.connect(destroyedSlot)
-        self.assertEqual(sys.getrefcount(destroyedSlot), 3)
+        self.assertEqual(sys.getrefcount(destroyedSlot), base_ref_count + 1)
         self.emitter.destroyed.disconnect(destroyedSlot)
-        self.assertEqual(sys.getrefcount(destroyedSlot), 2)
+        self.assertEqual(sys.getrefcount(destroyedSlot), base_ref_count)
 
 
 if __name__ == '__main__':
index 88c97f33db2e003c7c50bd36a60e85b4f94ca572..083763bd283d3ee92783a081924e16f9e8e55946 100644 (file)
@@ -36,13 +36,13 @@ class PythonSignalRefCount(unittest.TestCase):
         def cb(*args):
             pass
 
-        self.assertEqual(sys.getrefcount(cb), 2)
+        base_ref_count = sys.getrefcount(cb)
 
         self.emitter.foo.connect(cb)
-        self.assertEqual(sys.getrefcount(cb), 3)
+        self.assertEqual(sys.getrefcount(cb), base_ref_count + 1)
 
         self.emitter.foo.disconnect(cb)
-        self.assertEqual(sys.getrefcount(cb), 2)
+        self.assertEqual(sys.getrefcount(cb), base_ref_count)
 
 
 class CppSignalRefCount(unittest.TestCase):
@@ -58,13 +58,13 @@ class CppSignalRefCount(unittest.TestCase):
         def cb(*args):
             pass
 
-        self.assertEqual(sys.getrefcount(cb), 2)
+        base_ref_count = sys.getrefcount(cb)
 
         self.emitter.destroyed.connect(cb)
-        self.assertEqual(sys.getrefcount(cb), 3)
+        self.assertEqual(sys.getrefcount(cb), base_ref_count + 1)
 
         self.emitter.destroyed.disconnect(cb)
-        self.assertEqual(sys.getrefcount(cb), 2)
+        self.assertEqual(sys.getrefcount(cb), base_ref_count)
 
 
 if __name__ == '__main__':
index fbfea010e7422c8283e93c4e5b250f4ed98746e1..d3bf85530948696aa641d987596787d949fc7bf2 100644 (file)
@@ -30,8 +30,8 @@ class TestMetaObjectDump(unittest.TestCase):
         self._dir = Path(__file__).parent.resolve()
         pyside_root = self._dir.parents[4]
         self._metaobjectdump_tool = pyside_root / "sources" / "pyside-tools" / "metaobjectdump.py"
-        self._examples_dir = (pyside_root / "examples" /
-                              "qml" / "tutorials" / "extending-qml-advanced")
+        self._examples_dir = (pyside_root / "examples"
+                              "qml" / "tutorials" / "extending-qml-advanced")
 
         # Compile a list of examples (tuple [file, base line, command])
         examples = []
index 120c54af3fd28a39d26760a6caad45ca817ff8cc..14330726a1940e2171784d44c32482ccd658f8c4 100644 (file)
@@ -36,7 +36,7 @@ class DeployTestBase(unittest.TestCase):
         android_requirements_file = pyside_tools / "requirements-android.txt"
         with open(android_requirements_file, 'r', encoding='UTF-8') as file:
             while line := file.readline():
-                dependent_package = line.rstrip()
+                dependent_package = line.rstrip().split('==')[0]
                 if not bool(importlib.util.find_spec(dependent_package)):
                     command = [sys.executable, "-m", "pip", "install", dependent_package]
                     subprocess.run(command)
index a5d5bd25c0137e14c6743e50b367e3f7b2d50a0a..648b07a50832bd126391e0d6d818ec687632635f 100644 (file)
@@ -2,6 +2,7 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 from __future__ import annotations
 
+import json
 import unittest
 import tempfile
 import shutil
@@ -10,6 +11,7 @@ import os
 import importlib
 import platform
 from pathlib import Path
+from typing import Any
 from unittest.mock import patch
 from unittest import mock
 
@@ -21,35 +23,33 @@ init_test_paths(False)
 
 def is_pyenv_python():
     pyenv_root = os.environ.get("PYENV_ROOT")
-
     if pyenv_root and (resolved_exe := str(Path(sys.executable).resolve())):
         return resolved_exe.startswith(pyenv_root)
     return False
 
 
-class LongSortedOptionTest(unittest.TestCase):
+class DeployTestBase(unittest.TestCase):
     @staticmethod
-    def _option_prepare(s):
+    def _sort_command(command: str) -> list[str]:
         """
-        Take a string and return a list obtained by text.split().
-        Options starting with "--" are also sorted."
+        Given a command, returns a list obtained by text.split().
+        Options starting with "--" are also sorted.
         """
-        items = s.split()
+        items = command.split()
         for idx in range(len(items)):
             if items[idx].startswith("--"):
                 return items[:idx] + sorted(items[idx:])
         return items
 
-    def assertEqual(self, text_a, text_b):
-        if (not isinstance(text_a, str) or not isinstance(text_b, str)
-                or (len(text_a) < 50 and len(text_b) < 50)):
-            return super().assertEqual(text_a, text_b)
-        sort_a = self._option_prepare(text_a)
-        sort_b = self._option_prepare(text_b)
-        return super().assertEqual(sort_a, sort_b)
+    def assertCmdEqual(self, first: str, second: str, msg: Any = None):
+        """
+        Assert that two commands are equal. Sort their arguments
+        """
+        if not isinstance(first, str) or not isinstance(second, str):
+            return super().assertEqual(first, second, msg)
 
+        return super().assertEqual(self._sort_command(first), self._sort_command(second), msg)
 
-class DeployTestBase(LongSortedOptionTest):
     @classmethod
     def setUpClass(cls):
         cls.pyside_root = Path(__file__).parents[5].resolve()
@@ -64,6 +64,7 @@ class DeployTestBase(LongSortedOptionTest):
             sys.path.append(str(cls.pyside_root / "sources" / "pyside-tools"))
         cls.deploy_lib = importlib.import_module("deploy_lib")
         cls.deploy = importlib.import_module("deploy")
+        cls.project_lib = importlib.import_module("project_lib")
         sys.modules["deploy"] = cls.deploy
         files_to_ignore = [".cpp.o", ".qsb"]
         cls.dlls_ignore_nuitka = " ".join([f"--noinclude-dlls=*{file}"
@@ -106,7 +107,7 @@ class TestPySide6DeployWidgets(DeployTestBase):
                             "imageformats", "platforminputcontexts", "platforms",
                             "platformthemes", "styles", "xcbglintegrations"]
         # Plugins that needs to be passed to Nuitka
-        plugins_nuitka = ("platforminputcontexts")
+        plugins_nuitka = "platforminputcontexts"
         self.expected_run_cmd = (
             f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports"
             f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet"
@@ -128,11 +129,10 @@ class TestPySide6DeployWidgets(DeployTestBase):
 
     def testWidgetDryRun(self, mock_plugins):
         mock_plugins.return_value = self.all_plugins
-        # Checking for dry run commands is equivalent to mocking the
-        # subprocess.check_call() in commands.py as the the dry run command
-        # is the command being run.
+        # Checking for dry run commands is equivalent to mocking the subprocess.check_call()
+        # in commands.py as the dry run command is the command being run.
         original_output = self.deploy.main(self.main_file, dry_run=True, force=True)
-        self.assertEqual(original_output, self.expected_run_cmd)
+        self.assertCmdEqual(original_output, self.expected_run_cmd)
 
     @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
     def testWidgetConfigFile(self, mock_sitepackages, mock_plugins):
@@ -141,19 +141,19 @@ class TestPySide6DeployWidgets(DeployTestBase):
         # includes both dry run and config_file tests
         # init
         init_result = self.deploy.main(self.main_file, init=True, force=True)
-        self.assertEqual(init_result, None)
+        self.assertEqual(None, init_result)
 
         # test with config
         original_output = self.deploy.main(config_file=self.config_file, dry_run=True, force=True)
-        self.assertEqual(original_output, self.expected_run_cmd)
+        self.assertCmdEqual(original_output, self.expected_run_cmd)
 
-        # test config file contents
+        # test config file contents
         config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
         self.assertTrue(config_obj.get_value("app", "input_file").endswith("tetrix.py"))
-        self.assertTrue(config_obj.get_value("app", "project_dir").endswith("tetrix"))
+        self.assertEqual(config_obj.get_value("app", "project_dir"), ".")
         self.assertEqual(config_obj.get_value("app", "exec_directory"), ".")
         self.assertEqual(config_obj.get_value("python", "packages"),
-                         "Nuitka==2.5.1")
+                         "Nuitka==2.7.11")
         self.assertEqual(config_obj.get_value("qt", "qml_files"), "")
         equ_base = "--quiet --noinclude-qt-translations"
         equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base
@@ -170,7 +170,7 @@ class TestPySide6DeployWidgets(DeployTestBase):
 
     def testErrorReturns(self, mock_plugins):
         mock_plugins.return_value = self.all_plugins
-        # main file and config file does not exists
+        # Main file and config file do not exist
         fake_main_file = self.main_file.parent / "main.py"
         with self.assertRaises(RuntimeError) as context:
             self.deploy.main(main_file=fake_main_file, config_file=self.config_file)
@@ -178,13 +178,13 @@ class TestPySide6DeployWidgets(DeployTestBase):
 
     def testStandaloneMode(self, mock_plugins):
         mock_plugins.return_value = self.all_plugins
-        # remove --onefile from self.expected_run_cmd and replace it with --standalone
+        # Remove --onefile from self.expected_run_cmd and replace it with --standalone
         self.expected_run_cmd = self.expected_run_cmd.replace(" --onefile", " --standalone")
-        # test standalone mode
+        # Test standalone mode
         original_output = self.deploy.main(self.main_file, mode="standalone", dry_run=True,
                                            force=True)
 
-        self.assertEqual(original_output, self.expected_run_cmd)
+        self.assertCmdEqual(original_output, self.expected_run_cmd)
 
     @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
     def testExtraModules(self, mock_sitepackages, mock_plugins):
@@ -192,10 +192,10 @@ class TestPySide6DeployWidgets(DeployTestBase):
         mock_plugins.return_value = self.all_plugins
         init_result = self.deploy.main(self.main_file, extra_modules_grouped="QtNetwork,QtOpenGL",
                                        init=True, force=True)
-        self.assertEqual(init_result, None)
+        self.assertEqual(None, init_result)
         self.deploy.main(config_file=self.config_file, dry_run=True, force=True)
 
-        # test config file contents
+        # Test config file contents
         config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
         expected_modules = {"Core", "Gui", "Widgets", "Network", "OpenGL"}
         if sys.platform != "win32":
@@ -206,7 +206,7 @@ class TestPySide6DeployWidgets(DeployTestBase):
 
     @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
     def testExtraIgnoreDirs(self, mock_sitepackages, mock_plugins):
-        # create a directory to ignore
+        # Create a directory to ignore
         ignore_dir = self.temp_example_widgets / "ignore_dir"
         ignore_dir.mkdir()
         ignore_file = ignore_dir / "test_ignore.py"
@@ -220,7 +220,7 @@ class TestPySide6DeployWidgets(DeployTestBase):
         mock_plugins.return_value = self.all_plugins
         init_result = self.deploy.main(self.main_file, extra_ignore_dirs="ignore_dir",
                                        init=True, force=True)
-        self.assertEqual(init_result, None)
+        self.assertEqual(None, init_result)
         self.deploy.main(config_file=self.config_file, dry_run=True, force=True)
 
         config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
@@ -263,7 +263,7 @@ class TestPySide6DeployQml(DeployTestBase):
                             "platformthemes", "qmltooling", "tls",
                             "xcbglintegrations"]
         # Plugins that needs to be passed to Nuitka
-        plugins_nuitka = ("networkinformation,platforminputcontexts,qml,qmltooling")
+        plugins_nuitka = "networkinformation,platforminputcontexts,qml,qmltooling"
         self.expected_run_cmd = (
             f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports"
             f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet"
@@ -309,16 +309,17 @@ class TestPySide6DeployQml(DeployTestBase):
         with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
             mock_qmlimportscanner.return_value = ["QtQuick"]
             init_result = self.deploy.main(self.main_file, init=True, force=True)
-            self.assertEqual(init_result, None)
+            self.assertEqual(None, init_result)
 
         # test config file contents
         config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
         self.assertTrue(config_obj.get_value("app", "input_file").endswith("main.py"))
-        self.assertTrue(config_obj.get_value("app", "project_dir").endswith("editingmodel"))
+        self.assertEqual(config_obj.get_value("app", "project_dir"), ".")
         self.assertEqual(config_obj.get_value("app", "exec_directory"), ".")
         self.assertEqual(config_obj.get_value("python", "packages"),
-                         "Nuitka==2.5.1")
-        self.assertEqual(config_obj.get_value("qt", "qml_files"), "main.qml,MovingRectangle.qml")
+                         "Nuitka==2.7.11")
+        self.assertEqual(config_obj.get_value("qt", "qml_files"),
+                         "MovingRectangle.qml,main.qml")
         equ_base = "--quiet --noinclude-qt-translations"
         equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base
         self.assertEqual(config_obj.get_value("nuitka", "extra_args"), equ_value)
@@ -328,6 +329,12 @@ class TestPySide6DeployQml(DeployTestBase):
         )
         expected_modules = {"Core", "Gui", "Qml", "Quick", "Network", "OpenGL", "QmlModels",
                             "QmlWorkerScript", "QmlMeta"}
+
+        # Exclude OpenGL if the platform is Windows and architecture is aarch64
+        # https://bugreports.qt.io/browse/QTBUG-126030
+        if sys.platform == "win32" and platform.machine() == "ARM64":
+            expected_modules.remove("OpenGL")
+
         if sys.platform != "win32":
             expected_modules.add("DBus")
         obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
@@ -341,7 +348,7 @@ class TestPySide6DeployQml(DeployTestBase):
         with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
             mock_qmlimportscanner.return_value = ["QtQuick"]
             original_output = self.deploy.main(self.main_file, dry_run=True, force=True)
-            self.assertEqual(original_output, self.expected_run_cmd)
+            self.assertCmdEqual(original_output, self.expected_run_cmd)
             self.assertEqual(mock_qmlimportscanner.call_count, 1)
 
     def testMainFileDryRun(self, mock_plugins):
@@ -349,7 +356,7 @@ class TestPySide6DeployQml(DeployTestBase):
         with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
             mock_qmlimportscanner.return_value = ["QtQuick"]
             original_output = self.deploy.main(Path.cwd() / "main.py", dry_run=True, force=True)
-            self.assertEqual(original_output, self.expected_run_cmd)
+            self.assertCmdEqual(original_output, self.expected_run_cmd)
             self.assertEqual(mock_qmlimportscanner.call_count, 1)
 
 
@@ -380,7 +387,7 @@ class TestPySide6DeployWebEngine(DeployTestBase):
         main_file = self.temp_example_webenginequick / "quicknanobrowser.py"
         deployment_files = self.temp_example_webenginequick / "deployment"
         # Plugins that needs to be passed to Nuitka
-        plugins_nuitka = ("networkinformation,platforminputcontexts,qml,qmltooling")
+        plugins_nuitka = "networkinformation,platforminputcontexts,qml,qmltooling"
         qml_files = [
             "ApplicationRoot.qml",
             "BrowserDialog.qml",
@@ -432,7 +439,7 @@ class TestPySide6DeployWebEngine(DeployTestBase):
         with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
             mock_qmlimportscanner.return_value = ["QtQuick", "QtWebEngine"]
             init_result = self.deploy.main(main_file, init=True, force=True)
-            self.assertEqual(init_result, None)
+            self.assertEqual(None, init_result)
 
             # run dry_run
             original_output = self.deploy.main(main_file, dry_run=True, force=True)
@@ -450,8 +457,16 @@ class TestPySide6DeployWebEngine(DeployTestBase):
         expected_modules = {"Core", "Gui", "Quick", "Qml", "WebEngineQuick", "Network", "OpenGL",
                             "QmlModels", "QmlWorkerScript", "QmlMeta", "WebEngineCore",
                             "Positioning", "WebChannelQuick", "WebChannel"}
+
+        # Exclude specific modules if the platform is Windows and architecture is aarch64
+        if sys.platform == "win32" and platform.machine() == "ARM64":
+            excluded_modules = {"OpenGL", "WebEngineCore", "Positioning", "WebChannelQuick",
+                                "WebChannel"}
+            expected_modules.difference_update(excluded_modules)
+
         if sys.platform != "win32":
             expected_modules.add("DBus")
+
         obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
         self.assertEqual(obtained_modules, expected_modules)
 
@@ -495,12 +510,12 @@ class TestLongCommand(DeployTestBase):
 @unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11,
                  "Test only works on macOS version 12+")
 @patch("deploy_lib.config.QtDependencyReader.find_plugin_dependencies")
-class EmptyDSProjectTest(DeployTestBase):
+class TestEmptyDSProject(DeployTestBase):
     @classmethod
     def setUpClass(cls):
         super().setUpClass()
 
-        # setup a test DS Python project
+        # Set up a Qt Design Studio empty Python project
         base_path = Path(cls.temp_dir) / "PythonProject"
 
         project_name = "TestProject"
@@ -515,11 +530,16 @@ class EmptyDSProjectTest(DeployTestBase):
             base_path / f"{project_name}.qrc"
         ]
 
-        # Create the files
+        # Create the project files
         for file in files:
             file.parent.mkdir(parents=True, exist_ok=True)
             file.touch(exist_ok=True)
 
+        # Create a project file in the Python folder
+        cls.pyproject_path = (base_path / "Python" / ".pyproject").resolve()
+        cls.pyproject_path.touch()
+        cls.pyproject_path.write_text(json.dumps({"files": ["main.py", "autogen/settings.py"]}))
+
         cls.temp_example = base_path
 
     def setUp(self):
@@ -566,21 +586,25 @@ class EmptyDSProjectTest(DeployTestBase):
     def testDryRun(self, mock_plugins):
         with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:  # noqa: F841
             original_output = self.deploy.main(self.main_file, dry_run=True, force=True)
-            self.assertEqual(self.expected_run_cmd, original_output)
+            self.assertCmdEqual(self.expected_run_cmd, original_output)
 
     @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
     def testConfigFile(self, mock_sitepackages, mock_plugins):
         mock_sitepackages.return_value = Path(_get_qt_lib_dir())
-        # create config file
         with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:  # noqa: F841
+            # Create the pysidedeploy.spec file only
             init_result = self.deploy.main(self.main_file, init=True, force=True)
-            self.assertEqual(init_result, None)
+            self.assertEqual(None, init_result)
 
         # test config file contents
         config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
 
         self.assertTrue(config_obj.get_value("app", "input_file").endswith("main.py"))
         self.assertTrue(config_obj.get_value("app", "project_dir").endswith("PythonProject"))
+
+        expected_project_file = self.pyproject_path.relative_to(self.temp_example)
+        self.assertEqual(str(expected_project_file), config_obj.get_value("app", "project_file"))
+
         self.config_file.unlink()
 
 
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/.pyproject b/sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/.pyproject
deleted file mode 100644 (file)
index a5f654c..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-    "files": ["main.py", "autogen/settings.py"]
-}
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/Drumpad.pyproject b/sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/Drumpad.pyproject
new file mode 100644 (file)
index 0000000..a5f654c
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["main.py", "autogen/settings.py"]
+}
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/pyproject.toml
new file mode 100644 (file)
index 0000000..3eeb1a9
--- /dev/null
@@ -0,0 +1,5 @@
+[project]
+name = "Drumpad"
+
+[tool.pyside6-project]
+files = ["autogen/settings.py", "main.py"]
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/example_project.pyproject b/sources/pyside6/tests/tools/pyside6-project/example_project/example_project.pyproject
new file mode 100644 (file)
index 0000000..08fa3ea
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["mainwindow.py", "my_widget.py", "folder/file_in_folder.py", "main.py", "subproject/subproject.pyproject"]
+}
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/folder/label_in_folder.py b/sources/pyside6/tests/tools/pyside6-project/example_project/folder/label_in_folder.py
new file mode 100644 (file)
index 0000000..ea3a6f5
--- /dev/null
@@ -0,0 +1,9 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from PySide6.QtWidgets import QLabel
+
+
+class LabelInFolder(QLabel):
+    def __init__(self):
+        super().__init__()
+        self.setText("Label in folder")
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/main.py b/sources/pyside6/tests/tools/pyside6-project/example_project/main.py
new file mode 100644 (file)
index 0000000..0f7f967
--- /dev/null
@@ -0,0 +1,20 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+import os
+
+from mainwindow import MainWindow
+from PySide6.QtWidgets import QApplication
+import sys
+
+
+def main():
+    app = QApplication(sys.argv)
+    window = MainWindow()
+    if os.getenv("PYSIDE_TESTING"):
+        return 0
+    window.show()
+    return app.exec()
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/mainwindow.py b/sources/pyside6/tests/tools/pyside6-project/example_project/mainwindow.py
new file mode 100644 (file)
index 0000000..fc259c6
--- /dev/null
@@ -0,0 +1,22 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from PySide6.QtWidgets import QMainWindow, QWidget, QVBoxLayout
+from folder.label_in_folder import LabelInFolder
+from subproject.subproject_button import SubprojectButton
+
+
+class MainWindow(QMainWindow):
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self.setWindowTitle("Main Window")
+
+        self.central_layout = QVBoxLayout()
+        self.central_widget = QWidget()
+        self.setCentralWidget(self.central_widget)
+        self.central_widget.setLayout(self.central_layout)
+
+        self.label_in_folder = LabelInFolder()
+        self.central_layout.addWidget(self.label_in_folder)
+
+        self.subproject_button = SubprojectButton()
+        self.central_layout.addWidget(self.subproject_button)
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/example_project/pyproject.toml
new file mode 100644 (file)
index 0000000..59cc292
--- /dev/null
@@ -0,0 +1,5 @@
+[project]
+name = "example_project"
+
+[tool.pyside6-project]
+files = ["folder/file_in_folder.py", "main.py", "mainwindow.py", "my_widget.py", "subproject/subproject.pyproject"]
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml
new file mode 100644 (file)
index 0000000..1ceb0ac
--- /dev/null
@@ -0,0 +1,5 @@
+[project]
+name = "subproject"
+
+[tool.pyside6-project]
+files = ["subproject_button.py"]
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject
new file mode 100644 (file)
index 0000000..abfa1f4
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["subproject_button.py"]
+}
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject_button.py b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject_button.py
new file mode 100644 (file)
index 0000000..40813ff
--- /dev/null
@@ -0,0 +1,17 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from PySide6.QtWidgets import QPushButton, QApplication
+import sys
+
+
+class SubprojectButton(QPushButton):
+    def __init__(self):
+        super().__init__()
+        self.setText("Subproject button")
+
+
+if __name__ == "__main__":
+    app = QApplication(sys.argv)
+    button = SubprojectButton()
+    button.show()
+    sys.exit(app.exec())
diff --git a/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/existing_pyproject_toml.pyproject b/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/existing_pyproject_toml.pyproject
new file mode 100644 (file)
index 0000000..7ddbd86
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["zzz.py", "main.py"]
+}
diff --git a/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml
new file mode 100644 (file)
index 0000000..be8aa94
--- /dev/null
@@ -0,0 +1,22 @@
+[project]
+name = "my_project"
+version = "0.1.0"
+description = "A sample Python project"
+authors = [
+    { name = "John Doe", email = "john.doe@example.com" },
+]
+optional-dependencies = { dev = ["pytest", "black"], docs = ["sphinx"] }
+
+# Comment
+
+[tool.black]
+line-length = 88
+target-version = ["py38"]
+
+# Another comment
+
+[tool.pyside6-project]
+files = ["main.py", "zzz.py"]
+[build-system]
+requires = ["setuptools >=42"]
+build-backend = "setuptools.build_meta"
diff --git a/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/main.py b/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/main.py
new file mode 100644 (file)
index 0000000..8d4c966
--- /dev/null
@@ -0,0 +1,2 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
diff --git a/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/pyproject.toml
new file mode 100644 (file)
index 0000000..ce26565
--- /dev/null
@@ -0,0 +1,20 @@
+[project]
+name = "my_project"
+version = "0.1.0"
+description = "A sample Python project"
+authors = [
+    { name = "John Doe", email = "john.doe@example.com" },
+]
+optional-dependencies = { dev = ["pytest", "black"], docs = ["sphinx"] }
+
+# Comment
+
+[tool.black]
+line-length = 88
+target-version = ["py38"]
+
+# Another comment
+
+[build-system]
+requires = ["setuptools >=42"]
+build-backend = "setuptools.build_meta"
diff --git a/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/zzz.py b/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/zzz.py
new file mode 100644 (file)
index 0000000..8d4c966
--- /dev/null
@@ -0,0 +1,2 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
diff --git a/sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/invalid_pyproject.pyproject b/sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/invalid_pyproject.pyproject
new file mode 100644 (file)
index 0000000..6d6e84a
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["main.py", 33]
+}
diff --git a/sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/main.py b/sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/main.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/pyproject.toml
new file mode 100644 (file)
index 0000000..37d23c9
--- /dev/null
@@ -0,0 +1,2 @@
+this is not a valid pyproject.toml file
+because it does not have a valid toml structure
diff --git a/sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/valid_pyproject.pyproject b/sources/pyside6/tests/tools/pyside6-project/invalid_pyproject/valid_pyproject.pyproject
new file mode 100644 (file)
index 0000000..cc7a74a
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["main.py"]
+}
diff --git a/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/common_file.py b/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/common_file.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/expected_pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/expected_pyproject.toml
new file mode 100644 (file)
index 0000000..fda48f5
--- /dev/null
@@ -0,0 +1,5 @@
+[project]
+name = "multiple_pyproject"
+
+[tool.pyside6-project]
+files = ["common_file.py", "file1.py", "file2.py"]
diff --git a/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/file1.py b/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/file1.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/file2.py b/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/file2.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/project1.pyproject b/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/project1.pyproject
new file mode 100644 (file)
index 0000000..105f6f9
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["file1.py", "common_file.py"]
+}
diff --git a/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/project2.pyproject b/sources/pyside6/tests/tools/pyside6-project/multiple_pyproject/project2.pyproject
new file mode 100644 (file)
index 0000000..623b087
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["common_file.py", "file2.py"]
+}
index 6495abbdcc5f5fc339e6ea2c233aca688012b5eb..d663952519c6e1c9ebcc60e2d5d7dc337246ec0c 100644 (file)
@@ -1,16 +1,18 @@
 # Copyright (C) 2024 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 import contextlib
+import difflib
+import importlib
 import io
 import os
 import shutil
 import sys
-import unittest
-from unittest import mock
-from unittest import TestCase
 import tempfile
-import importlib
+import unittest
 from pathlib import Path
+from unittest import TestCase
+from unittest import mock
+from unittest.mock import patch
 
 sys.path.append(str(Path(__file__).resolve().parents[2]))
 from init_paths import init_test_paths
@@ -18,11 +20,30 @@ from init_paths import init_test_paths
 init_test_paths(False)
 
 
+def file_diff(expected_file: Path, actual_file: Path) -> str:
+    """
+    Get a unified diff between two files
+    """
+    target_text = expected_file.read_text(encoding="utf-8").splitlines()
+    generated_text = actual_file.read_text(encoding="utf-8").splitlines()
+
+    return "\n".join(difflib.unified_diff(
+        generated_text, target_text,
+        fromfile=str(actual_file),
+        tofile=str(expected_file),
+        lineterm=""
+    ))
+
+
 class PySide6ProjectTestBase(TestCase):
+    # If a project name is specified, on each the test, the project folder will be copy to the
+    # temp dir and the current dir will be changed to the project folder
+    # The project name must match an existing folder in the folder where this file is located
+    project_name: str | None = None
+
     @classmethod
     def setUpClass(cls):
         cls.pyside_root = Path(__file__).parents[5].resolve()
-        cls.example_root = cls.pyside_root / "examples"
         tools_path = cls.pyside_root / "sources" / "pyside-tools"
         if tools_path not in sys.path:
             sys.path.append(str(tools_path))
@@ -32,36 +53,42 @@ class PySide6ProjectTestBase(TestCase):
         cls.current_dir = Path.cwd()
         # print no outputs to stdout
         sys.stdout = mock.MagicMock()
+        if cls.project_name:
+            cls.temp_project = Path(cls.temp_dir / cls.project_name).resolve()
+        os.chdir(cls.temp_dir)
+
+    def setUp(self):
+        super().setUp()
+        if self.project_name:
+            shutil.copytree(Path(__file__).parent / self.project_name, self.temp_project)
+            os.chdir(self.temp_project)
+
+    def tearDown(self):
+        super().tearDown()
+        if self.project_name:
+            os.chdir(self.temp_dir)
+            shutil.rmtree(self.temp_project)
 
     @classmethod
     def tearDownClass(cls):
         os.chdir(cls.current_dir)
         shutil.rmtree(cls.temp_dir)
 
-    def setUp(self):
-        os.chdir(self.temp_dir)
-
 
 class TestPySide6ProjectDesignStudio(PySide6ProjectTestBase):
-    @classmethod
-    def setUpClass(cls):
-        super().setUpClass()
-        example_drumpad = Path(__file__).parent / "example_drumpad"
-        cls.temp_example_drumpad = Path(
-            shutil.copytree(example_drumpad, cls.temp_dir / "drumpad")
-        ).resolve()
+    project_name = "example_drumpad"
 
     def testDrumpadExample(self):
         # This test compiles the .qrc file into a .py file and checks whether the compilation is
         # carried out only when required
-        compiled_resources_path = self.temp_example_drumpad / "Python" / "autogen" / "resources.py"
-        resources_path = self.temp_example_drumpad / "Drumpad.qrc"
-        requires_rebuild = self.project_lib.utils.requires_rebuild
+        compiled_resources_path = Path("Python") / "autogen" / "resources.py"
+        resources_path = Path("Drumpad.qrc")
 
+        requires_rebuild = self.project_lib.utils.requires_rebuild
+        pyproject_path = Path("Python") / "Drumpad.pyproject"
         self.assertFalse(compiled_resources_path.exists())
-
-        os.chdir(self.temp_example_drumpad / "Python")
-        self.project.main(mode="build")
+        self.assertTrue(pyproject_path.exists())
+        self.project.main(mode="build", project_path=pyproject_path)
 
         self.assertTrue(compiled_resources_path.exists())
         self.assertFalse(requires_rebuild([resources_path], compiled_resources_path))
@@ -72,86 +99,373 @@ class TestPySide6ProjectDesignStudio(PySide6ProjectTestBase):
 
         self.assertTrue(requires_rebuild([resources_path], compiled_resources_path))
 
-        self.project.main(mode="build")
+        self.project.main(mode="build", project_path=pyproject_path)
 
         self.assertFalse(requires_rebuild([resources_path], compiled_resources_path))
 
         # Refresh the modification timestamp of one of the resources files
-        list((self.temp_example_drumpad / "Resources").glob("*.txt"))[0].touch()
+        list((Path("Resources").glob("*.txt")))[0].touch()
         self.assertTrue(requires_rebuild([resources_path], compiled_resources_path))
 
-        self.project.main(mode="clean")
+        self.project.main(mode="clean", project_path=pyproject_path)
 
         self.assertFalse(compiled_resources_path.exists())
 
+    def testMigrateDrumpadExample(self):
+        # The pyproject.toml file already contains the expected output
+        expected_pyproject_toml = Path("Python") / "pyproject.toml"
+        expected_pyproject_toml.rename(expected_pyproject_toml.parent / "expected_pyproject.toml")
+        existing_pyproject = Path("Python") / "Drumpad.pyproject"
+
+        with self.assertRaises(SystemExit) as context:
+            with patch("builtins.input", return_value="y"):
+                self.project.main(mode="migrate-pyproject",
+                                  project_path=existing_pyproject.as_posix())
+
+        self.assertEqual(0, context.exception.code)
+        generated_pyproject_toml = Path("Python") / "pyproject.toml"
+        self.assertTrue(generated_pyproject_toml.exists())
+        diff = file_diff(expected_pyproject_toml, generated_pyproject_toml)
+        self.assertFalse(diff, f"Generated pyproject.toml does not match:\n{diff}")
+
 
 class TestPySide6ProjectNew(PySide6ProjectTestBase):
     def testNewUi(self):
+        test_project_path = self.temp_dir / "NewUiProject"
         with self.assertRaises(SystemExit) as context:
-            self.project.main(mode="new-ui", file="TestProject")
-        test_project_path = Path("TestProject")
-        self.assertTrue((test_project_path / "TestProject.pyproject").exists())
+            self.project.main(mode="new-ui", project_dir=test_project_path.as_posix())
+
+        self.assertTrue((test_project_path / "pyproject.toml").exists())
         self.assertTrue((test_project_path / "mainwindow.ui").exists())
         self.assertTrue((test_project_path / "main.py").exists())
-        self.assertEqual(context.exception.code, 0)
+        self.assertEqual(0, context.exception.code)
         shutil.rmtree(test_project_path)
 
-    def testRaiseErrorOnExistingProject(self):
+    def testRaiseErrorOnExistingNonEmptyProject(self):
+        # Create a project twice to ensure that an error is raised
+        project_name = "TestProject"
         with self.assertRaises(SystemExit) as context:
-            self.project.main(mode="new-ui", file="TestProject")
-        self.assertEqual(context.exception.code, 0)
+            self.project.main(mode="new-ui", project_dir=project_name)
+
+        self.assertEqual(0, context.exception.code)
+
         error_message = io.StringIO()
-        with self.assertRaises(SystemExit) as context, contextlib.redirect_stderr(error_message):
-            self.project.main(mode="new-ui", file="TestProject")
-        self.assertEqual(context.exception.code, -1)
-        self.assertTrue(error_message.getvalue())  # some error message is printed
+        with self.assertRaises(SystemExit) as context:
+            with contextlib.redirect_stderr(error_message):
+                self.project.main(mode="new-ui", project_dir=project_name)
+
+        self.assertEqual(1, context.exception.code)
+        self.assertTrue(f"Can not create project at {project_name}: directory is not empty." in
+                        error_message.getvalue())
         shutil.rmtree(self.temp_dir / "TestProject")
 
+    def testRaiseErrorOnInvalidProjectName(self):
+        # Create a project with an empty project name
+        error_message = io.StringIO()
+        with self.assertRaises(SystemExit) as context:
+            with contextlib.redirect_stderr(error_message):
+                self.project.main(mode="new-ui", project_dir="asdf/?^%$#@!")
+
+        self.assertEqual(1, context.exception.code)
+        self.assertTrue("Invalid project name" in error_message.getvalue())
+
     def testNewQuick(self):
+        test_project_path = Path("QuickProject")
+
         with self.assertRaises(SystemExit) as context:
-            self.project.main(mode="new-quick", file="TestProject")
-        test_project_path = Path("TestProject")
-        self.assertTrue((test_project_path / "TestProject.pyproject").exists())
+            self.project.main(mode="new-quick", project_dir=str(test_project_path))
+
+        self.assertTrue((test_project_path / "pyproject.toml").exists())
         self.assertTrue((test_project_path / "main.qml").exists())
         self.assertTrue((test_project_path / "main.py").exists())
-        self.assertEqual(context.exception.code, 0)
+        self.assertEqual(0, context.exception.code)
         shutil.rmtree(test_project_path)
 
     def testNewWidget(self):
+        project_dir = self.temp_dir / "inner_folder" / "another_folder" / "WidgetProject"
         with self.assertRaises(SystemExit) as context:
-            self.project.main(mode="new-widget", file="TestProject")
-        test_project_path = Path("TestProject")
-        self.assertTrue((test_project_path / "TestProject.pyproject").exists())
-        self.assertTrue((test_project_path / "main.py").exists())
-        self.assertEqual(context.exception.code, 0)
-        shutil.rmtree(test_project_path)
+            self.project.main(mode="new-widget", project_dir=project_dir.as_posix())
+        self.assertTrue((project_dir / "pyproject.toml").exists())
+        self.assertTrue((project_dir / "main.py").exists())
+        self.assertEqual(0, context.exception.code)
+        shutil.rmtree(project_dir)
 
     def testRaiseErrorWhenNoProjectNameIsSpecified(self):
+        mode = "new-widget"
         error_message = io.StringIO()
         with self.assertRaises(SystemExit) as context, contextlib.redirect_stderr(error_message):
-            self.project.main(mode="new-widget", file="")
+            self.project.main(mode=mode)
         self.assertEqual(context.exception.code, 1)
-        self.assertTrue(error_message.getvalue())  # some error message is printed
+        expected_msg = f"Error creating new project: {mode} requires a directory name or path"
+        self.assertTrue(expected_msg in error_message.getvalue())
+
+    def testCreateProjectLegacyPyProjectFile(self):
+        project_path = Path("TestPyProjectJSON")
+        mode = "new-widget"
+        with self.assertRaises(SystemExit) as context:
+            self.project.main(mode=mode, project_dir=project_path.as_posix(), legacy_pyproject=True)
+        self.assertEqual(0, context.exception.code)
+        self.assertTrue((project_path / "main.py").exists())
+        self.assertTrue((project_path / f"{project_path.name}.pyproject").exists())
 
 
 class TestPySide6ProjectRun(PySide6ProjectTestBase):
-    @classmethod
-    def setUpClass(cls):
-        super().setUpClass()
-        example_widgets = cls.example_root / "widgets" / "widgets" / "tetrix"
-        cls.temp_example_tetrix = Path(
-            shutil.copytree(example_widgets, Path(cls.temp_dir) / "tetrix")
-        ).resolve()
-
-    def testRunEmptyProject(self):
-        project_folder = self.temp_dir / "TestProject"
+    project_name = "example_project"
+
+    def testRaiseErrorWhenRunningEmptyProject(self):
+        # Create a new empty project in the temp dir
+        project_folder = self.temp_dir / "empty_project"
         project_folder.mkdir()
         os.chdir(project_folder)
+
         error_message = io.StringIO()
-        with self.assertRaises(SystemExit) as context, contextlib.redirect_stderr(error_message):
+        with self.assertRaises(SystemExit) as context:
+            with contextlib.redirect_stderr(error_message):
+                self.project.main(mode="run")
+
+        os.chdir(self.temp_dir)
+        shutil.rmtree(project_folder)
+
+        self.assertEqual(1, context.exception.code)
+        self.assertTrue("No project file found" in error_message.getvalue())
+
+    def testRunExampleProject(self):
+        # The project is executed in a subprocess. The proejct code reads the PYSIDE_TESTING
+        # environment variable to avoid starting the Qt event loop
+        os.environ["PYSIDE_TESTING"] = "1"
+        with self.assertRaises(SystemExit) as context:
             self.project.main(mode="run")
-        self.assertEqual(context.exception.code, 1)
-        self.assertTrue(error_message.getvalue())  # some error message is printed
+        os.environ.pop("PYSIDE_TESTING")
+        self.assertEqual(0, context.exception.code)
+
+        self.assertEqual(Path("pyproject.toml").resolve(),
+                         self.project_lib.resolve_valid_project_file())
+
+
+class TestPySide6ProjectExampleProject(PySide6ProjectTestBase):
+    """
+    Test of an example project with both pyproject.toml and .pyproject valid files.
+    Contains a subproject with its own pyproject.toml file and .pyproject file too
+    """
+    project_name = "example_project"
+
+    def testMigratePyProjectToToml(self):
+        # The existing pyproject.toml file contains the expected output
+        expected_pyproject_toml = Path("pyproject.toml").rename("expected_pyproject.toml")
+        expected_subproject_pyproject_toml = Path("subproject") / "pyproject.toml"
+        expected_subproject_pyproject_toml.rename(
+            expected_subproject_pyproject_toml.parent / "expected_subproject_pyproject.toml")
+
+        with self.assertRaises(SystemExit) as context:
+            self.project.main(mode="migrate-pyproject")
+
+        self.assertEqual(0, context.exception.code)
+
+        generated_pyproject_toml = Path("pyproject.toml")
+        self.assertTrue(generated_pyproject_toml.exists())
+        diff = file_diff(expected_pyproject_toml, generated_pyproject_toml)
+        self.assertFalse(diff, f"Generated pyproject.toml does not match:\n{diff}")
+
+        generated_subproject_pyproject_toml = Path("subproject") / "pyproject.toml"
+        self.assertTrue(generated_subproject_pyproject_toml.exists())
+        diff = file_diff(expected_subproject_pyproject_toml, generated_subproject_pyproject_toml)
+        self.assertFalse(diff, f"Generated subproject/pyproject.toml does not match:\n{diff}")
+
+    def testMigratePyProjectToTomlSpecifyingPyProjectFile(self):
+        # The existing pyproject.toml file contains the expected output
+        existing_pyproject = Path("example_project.pyproject")
+        expected_pyproject_toml = Path("pyproject.toml")
+        expected_pyproject_toml.rename("example_project.toml")
+
+        expected_subproject_pyproject_toml = Path("subproject") / "pyproject.toml"
+        expected_subproject_pyproject_toml.rename(
+            expected_subproject_pyproject_toml.parent / "expected_pyproject.toml")
+
+        with self.assertRaises(SystemExit) as context:
+            with patch("builtins.input", return_value="y"):
+                self.project.main(mode="migrate-pyproject",
+                                  project_path=existing_pyproject.as_posix())
+
+        self.assertEqual(0, context.exception.code)
+
+        generated_pyproject_toml = Path("pyproject.toml")
+        self.assertTrue(generated_pyproject_toml.exists())
+        diff = file_diff(expected_pyproject_toml, generated_pyproject_toml)
+        self.assertFalse(diff, f"Generated pyproject.toml does not match:\n{diff}")
+
+        generated_subproject_pyproject_toml = Path("subproject") / "pyproject.toml"
+        self.assertTrue(generated_subproject_pyproject_toml.exists())
+        diff = file_diff(expected_subproject_pyproject_toml, generated_subproject_pyproject_toml)
+        self.assertFalse(diff, f"Generated subproject/pyproject.toml does not match:\n{diff}")
+
+
+class TestPySide6ProjectExistingPyProjectToml(PySide6ProjectTestBase):
+    """
+    Test for migrating a project with an existing pyproject.toml file which does not contain the
+    [tool.pyside6-project] section
+    """
+    project_name = "existing_pyproject_toml"
+
+    def testMigratePyProjectToTomlAlreadyExistingTomlFile(self):
+        with self.assertRaises(SystemExit) as context:
+            with patch("builtins.input", return_value="y"):
+                self.project.main(mode="migrate-pyproject")
+
+        self.assertEqual(0, context.exception.code)
+        diff = file_diff(Path("expected_pyproject.toml"),
+                         Path("pyproject.toml"))
+        self.assertFalse(diff, f"Updated pyproject.toml does not match:\n{diff}")
+
+
+class TestPySide6ProjectInvalidPyProjectToml(PySide6ProjectTestBase):
+    """
+    Check the current behavior in a project with an existing invalid pyproject.toml file and
+    invalid_pyproject.pyproject file
+    """
+
+    project_name = "invalid_pyproject"
+
+    def testRunInvalidPyProjectTomlFile(self):
+        pyproject_toml = Path("pyproject.toml")
+        self.assertTrue(pyproject_toml.exists())
+        self.assertTrue(self.project_lib.parse_pyproject_toml(pyproject_toml).errors)
+
+        error_message = io.StringIO()
+        with contextlib.redirect_stderr(error_message):
+            with self.assertRaises(SystemExit) as context:
+                self.project.main(mode="run", project_path=pyproject_toml.as_posix())
+
+        self.assertEqual(1, context.exception.code)
+        self.assertTrue("Invalid project file" in error_message.getvalue())
+
+    def testRunSpecifyingPyProjectJsonFile(self):
+        # Check that the *.pyproject file is used if the pyproject.toml is invalid when using
+        # pyside6-project run
+
+        pyproject_toml_file = Path("pyproject.toml")
+        self.assertTrue(pyproject_toml_file.exists())
+        # Ensure that pyproject.toml is considered invalid
+        self.assertTrue(self.project_lib.parse_pyproject_toml(pyproject_toml_file).errors)
+
+        valid_pyproject = Path("valid_pyproject.pyproject")
+        self.assertTrue(valid_pyproject.exists())
+
+        # Ensure that the project can still be run specifying a valid *.pyproject JSON file
+        with self.assertRaises(SystemExit) as context:
+            self.project.main(mode="run", project_path=valid_pyproject.as_posix())
+
+        self.assertEqual(0, context.exception.code)
+        self.assertTrue(Path("main.py").exists())
+
+    def testErrorRaisesWhenRunningWithoutSpecifyingProjectFile(self):
+        # The project folder contains two *.pyproject JSON files.
+        # The tool should raise an error because the project file is not specified
+        error_message = io.StringIO()
+        with contextlib.redirect_stderr(error_message):
+            with self.assertRaises(SystemExit) as context:
+                self.project.main(mode="run")
+        self.assertEqual(1, context.exception.code)
+        self.assertTrue("Multiple project files found" in error_message.getvalue())
+
+    def testRaiseErrorResolvingInvalidProjectFile(self):
+        # Simulate that the user is specifying an invalid project file
+        invalid_pyproject_file = Path("invalid_pyproject.pyproject")
+        self.assertTrue(invalid_pyproject_file.exists())
+
+        with self.assertRaises(ValueError) as context:
+            self.project_lib.resolve_valid_project_file(invalid_pyproject_file.as_posix())
+
+        exception_message = str(context.exception)
+        self.assertTrue("Invalid project file" in exception_message)
+        self.assertTrue(str(invalid_pyproject_file) in exception_message)
+
+    def testResolveValidProjectFile(self):
+        # Simulate that the user is specifying a valid project file
+        valid_pyproject_file = Path("valid_pyproject.pyproject")
+        actual_project_file = self.project_lib.resolve_valid_project_file(
+            valid_pyproject_file.as_posix())
+        self.assertEqual(valid_pyproject_file.resolve(), actual_project_file)
+
+    def testRaiseErrorResolvingInvalidPyProjectToml(self):
+        # Simulate that the user is specifying an invalid pyproject.toml file
+        pyproject_toml_file = Path("pyproject.toml")
+        self.assertTrue(pyproject_toml_file.exists())
+
+        with self.assertRaises(ValueError) as context:
+            self.project_lib.resolve_valid_project_file(pyproject_toml_file.as_posix())
+
+        exception_message = str(context.exception)
+        self.assertTrue("Invalid project file" in exception_message)
+        self.assertTrue(str(pyproject_toml_file) in exception_message)
+
+    def testMigrateInvalidPyProjectToml(self):
+        # Can not migrate a project with an invalid pyproject.toml file
+        error_message = io.StringIO()
+        with contextlib.redirect_stderr(error_message):
+            with self.assertRaises(SystemExit) as context:
+                with patch("builtins.input", return_value="y"):
+                    self.project.main(mode="migrate-pyproject")
+
+        self.assertEqual(1, context.exception.code)
+        self.assertTrue("Invalid project file" in error_message.getvalue())
+
+    def testMigrateInvalidPyProjectTomlSpecifyingWrongFile(self):
+        # Test specifying the pyproject.toml file as the project file to be migrated
+        existing_invalid_pyproject_toml = Path("pyproject.toml")
+        self.assertTrue(
+            bool(self.project_lib.parse_pyproject_toml(existing_invalid_pyproject_toml).errors))
+
+        error_message = io.StringIO()
+        with contextlib.redirect_stderr(error_message):
+            with self.assertRaises(SystemExit) as context:
+                self.project.main(mode="migrate-pyproject",
+                                  project_path=existing_invalid_pyproject_toml)
+
+        self.assertEqual(1, context.exception.code)
+        self.assertTrue("Cannot migrate non \"*.pyproject\" file" in error_message.getvalue())
+        self.assertTrue("pyproject.toml" in error_message.getvalue())
+
+
+def testRunInvalidPyProjectToml(self):
+    # Ensure that the .pyproject file is preferred over the invalid pyproject.toml file.
+    # This preserves the backward compatibility of the .pyproject file
+
+    # Remove the invalid invalid_pyproject.pyproject file first
+    Path("invalid_pyproject.pyproject").unlink()
+    self.assertFalse(Path("invalid_pyproject.pyproject").exists())
+
+    with self.assertRaises(SystemExit) as context:
+        self.project.main(mode="run")
+
+    self.assertEqual(0, context.exception.code)
+    self.assertEqual(Path("valid_pyproject.pyproject").resolve(),
+                     self.project_lib.resolve_valid_project_file())
+
+
+class TestPySide6ProjectMultiplePyProject(PySide6ProjectTestBase):
+    project_name = "multiple_pyproject"
+
+    def testCancelMigration(self):
+        # Ensure that the pyproject.toml is not created if the user cancels the operation
+        with self.assertRaises(SystemExit) as context:
+            with patch("builtins.input", return_value="n"):
+                self.project.main(mode="migrate-pyproject")
+
+        self.assertEqual(0, context.exception.code)
+        self.assertFalse(Path("pyproject.toml").exists())
+
+    def testMigrateMultiplePyProjectFilesToToml(self):
+        expected_pyproject_toml = Path("expected_pyproject.toml")
+        generated_pyproject_toml = Path("pyproject.toml")
+
+        with self.assertRaises(SystemExit) as context:
+            with patch("builtins.input", return_value="y"):
+                self.project.main(mode="migrate-pyproject")
+
+        self.assertEqual(0, context.exception.code)
+        self.assertTrue(generated_pyproject_toml.exists())
+        diff = file_diff(expected_pyproject_toml, generated_pyproject_toml)
+        self.assertFalse(diff, f"Generated pyproject.toml does not match:\n{diff}")
 
 
 if __name__ == "__main__":
index 6015ebc8894ed9b8bd1ed471c92a57eeaa586498..a84734d296af8559c4b1648ac901b8f77d82bd08 100644 (file)
@@ -26,8 +26,8 @@ class TestPySide6QmlUnit(unittest.TestCase):
         self.pyside_root = self._dir.parents[4]
 
         self.pyqml_path = self.pyside_root / "sources" / "pyside-tools" / "qml.py"
-        self.core_qml_path = (self.pyside_root / "examples" / "qml" /
-                              "tutorials" / "extending-qml-advanced" / "adding")
+        self.core_qml_path = (self.pyside_root / "examples" / "qml"
+                              "tutorials" / "extending-qml-advanced" / "adding")
 
         self.pyqml_run_cmd = [sys.executable, os.fspath(self.pyqml_path)]
 
index b495eaead8ba060fbc5c6ea89d1d8d7c7d9a4c3e..d7ac56cc240e6807ec6ec189d6a1b41091c18291 100644 (file)
@@ -16,7 +16,7 @@ class BasicPySlotCase:
     def tearDown(self):
         try:
             del self.args
-        except:
+        except:  # noqa: E722
             pass
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
index ee95d9a0f1094b54dfb6b96feba57be51b975127..318debea84643de25fc7f66de408d817d1c0426f 100644 (file)
@@ -37,7 +37,7 @@ class DocModifier(type):
         # FIXME currently we have to define doc_filter on each subclass
         filter_function = dct.get('doc_filter')
         if not filter_function:
-            filter_function = lambda x: False
+            filter_function = lambda x: False  # noqa: E731
 
         for base in bases:
             for attr in [x for x in base.__dict__ if filter_function(x)]:
@@ -45,9 +45,9 @@ class DocModifier(type):
 
                 if original.__doc__:
                     copy = copy_func(original)
-                    copy.__doc__ = (dct.get('doc_prefix', '') +
-                                    original.__doc__ +
-                                    dct.get('doc_suffix', ''))
+                    copy.__doc__ = (dct.get('doc_prefix', '')
+                                    + original.__doc__
+                                    dct.get('doc_suffix', ''))
                     dct[attr] = copy
 
         return type.__new__(mcs, name, bases, dct)
@@ -70,23 +70,23 @@ if __name__ == '__main__':
 
     class Implementing(BaseTest):
 
-        doc_filter = lambda x: x.startswith('test')
+        doc_filter = lambda x: x.startswith('test')  # noqa: E731
         doc_prefix = 'prefix'
         doc_suffix = 'suffix'
 
     class OnlyPrefix(BaseTest):
 
-        doc_filter = lambda x: x.startswith('test')
+        doc_filter = lambda x: x.startswith('test')  # noqa: E731
         doc_prefix = 'prefix'
 
     class OnlySuffix(BaseTest):
 
-        doc_filter = lambda x: x.startswith('test')
+        doc_filter = lambda x: x.startswith('test')  # noqa: E731
         doc_suffix = 'suffix'
 
     assert (Implementing.testBase.__doc__ == 'prefixbasesuffix')
-    assert (Implementing.testWithoutDoc.__doc__ == None)
+    assert (Implementing.testWithoutDoc.__doc__ is None)
     assert (OnlySuffix.testBase.__doc__ == 'basesuffix')
-    assert (OnlySuffix.testWithoutDoc.__doc__ == None)
+    assert (OnlySuffix.testWithoutDoc.__doc__ is None)
     assert (OnlyPrefix.testBase.__doc__ == 'prefixbase')
-    assert (OnlyPrefix.testWithoutDoc.__doc__ == None)
+    assert (OnlyPrefix.testWithoutDoc.__doc__ is None)
index 44723d392779e2afd381d5c2a71cae2917f0b298..38f6277f7e5c672008a2003cdda0d7aa575766a5 100644 (file)
@@ -26,8 +26,8 @@ class UsesQApplication(unittest.TestCase):
                                     if _.endswith((".QtCore", ".QtGui", ".QtWidgets")))[-1]]
         found = module.__name__.rsplit(".")[-1]
         cls = getattr(module, {"QtWidgets": "QApplication",
-                               "QtGui":     "QGuiApplication",
-                               "QtCore":    "QCoreApplication"}[found])
+                               "QtGui": "QGuiApplication",
+                               "QtCore": "QCoreApplication"}[found])
         # Simple way of making instance a singleton
         super().setUp()
         self.app = cls.instance() or cls([])
diff --git a/sources/pyside6/tests/util/processtimer.py b/sources/pyside6/tests/util/processtimer.py
deleted file mode 100644 (file)
index 9054055..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env python
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-from __future__ import annotations
-
-import os
-import time
-
-
-class TimeoutException(Exception):
-    def __init__(self, msg):
-        self.msg = msg
-
-    def __str__(self):
-        return repr(self.msg)
-
-
-class ProcessTimer:
-    '''Timeout function for controlling a subprocess.Popen instance.
-
-    Naive implementation using busy loop, see later other means
-    of doing this.
-    '''
-
-    def __init__(self, proc, timeout):
-        self.proc = proc
-        self.timeout = timeout
-
-    def waitfor(self):
-        time_passed = 0
-        while (self.proc.poll() is None and time_passed < self.timeout):
-            time_passed = time_passed + 1
-            time.sleep(1)
-
-        if time_passed >= self.timeout:
-            raise TimeoutException("Timeout expired, possible deadlock")
-
-
-if __name__ == "__main__":
-    # simple example
-
-    from subprocess import Popen
-
-    proc = Popen(['sleep', '10'])
-    t = ProcessTimer(proc, 5)
-    try:
-        t.waitfor()
-    except TimeoutException:
-        print(f"timeout - PID: {t.proc.pid}")
-        #TODO: detect SO and kill accordingly
-        #Linux
-        os.kill(t.proc.pid, 9)
-        #Windows (not tested)
-        #subprocess.Popen("taskkill /F /T /PID %i"%handle.pid , shell=True)
-    print(f"exit code: {t.proc.poll()}")
diff --git a/sources/pyside6/tests/util/test_processtimer.py b/sources/pyside6/tests/util/test_processtimer.py
deleted file mode 100644 (file)
index 417db8b..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-from __future__ import annotations
-
-'Tests for processtimer.py'
-
-import unittest
-import os
-
-from subprocess import Popen, PIPE
-from processtimer import TimeoutException, ProcessTimer
-
-
-class TimeoutTest(unittest.TestCase):
-
-    def tearDown(self):
-        try:
-            os.kill(self.proc.pid, 9)
-        except OSError:
-            pass
-
-    def testRaise(self):
-        self.proc = Popen(['python2.5', '-c', 'while True: pass'], stdout=PIPE, stderr=PIPE)
-        timer = ProcessTimer(self.proc, 1)
-        self.assertRaises(TimeoutException, timer.waitfor)
-
-
-class SimpleTest(unittest.TestCase):
-
-    def tearDown(self):
-        try:
-            os.kill(self.proc.pid, 9)
-        except OSError:
-            pass
-
-    def testSimple(self):
-        self.proc = Popen(['python2.5', '-c', '"print"'], stdout=PIPE, stderr=PIPE)
-        timer = ProcessTimer(self.proc, 10)
-        timer.waitfor()
-
-
-class TestEchoOutput(unittest.TestCase):
-
-    def tearDown(self):
-        try:
-            os.kill(self.proc.pid, 9)
-        except OSError:
-            pass
-
-    def testOutput(self):
-        self.proc = Popen(['python2.5', '-c', 'print 1',], stdout=PIPE, stderr=PIPE)
-        timer = ProcessTimer(self.proc, 1)
-        timer.waitfor()
-        self.assertEqual(self.proc.stdout.read().strip(), '1')
-
-
-class TestRetCode(unittest.TestCase):
-
-    def tearDown(self):
-        try:
-            os.kill(self.proc.pid, 9)
-        except OSError:
-            pass
-
-    def testSimple(self):
-        self.proc = Popen(['python2.5', '-c', 'print 1'], stdout=PIPE, stderr=PIPE)
-        timer = ProcessTimer(self.proc, 10)
-        timer.waitfor()
-
-        self.assertEqual(self.proc.poll(), 0)
-
-
-if __name__ == '__main__':
-    unittest.main()
index 8c7d76c02c76c58a9856115e77de1e6ac71b7981..248b66f5f5c95ec8fa90794a08eded8fa7fdc3df 100644 (file)
@@ -1,5 +1,5 @@
 set(shiboken_MAJOR_VERSION "6")
-set(shiboken_MINOR_VERSION "8")
-set(shiboken_MICRO_VERSION "2.1")
+set(shiboken_MINOR_VERSION "9")
+set(shiboken_MICRO_VERSION "2")
 set(shiboken_PRE_RELEASE_VERSION_TYPE "")
 set(shiboken_PRE_RELEASE_VERSION "")
index 37758f7f474e8fd6a224367bf9006308b427b92e..c87bf5feaef46c054568aa2ab5514b99385d72c7 100644 (file)
@@ -37,6 +37,7 @@ dependency.h
 documentation.cpp documentation.h
 documentation_enums.h
 dotview.cpp dotview.h
+filecache.cpp filecache.h
 enclosingclassmixin.cpp enclosingclassmixin.h
 enumtypeentry.h
 enumvaluetypeentry.h
index 05cebe10ada5c388786fda5ef6762c32a82898d4..8e8825ba5ef6c8a8e3ce3c4239e3f66989ea80c8 100644 (file)
@@ -7,8 +7,8 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QSharedData>
+#include <QtCore/qdebug.h>
+#include <QtCore/qshareddata.h>
 
 using namespace Qt::StringLiterals;
 
index 38402e3699f0cac733934b4c80973549f7b35e3b..00723743a23fdc1b7dd2afc06735844c3e68a6c7 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef ABSTRACTMETAARGUMENT_H
 #define ABSTRACTMETAARGUMENT_H
 
-#include <QtCore/QSharedDataPointer>
+#include <QtCore/qshareddata.h>
 
 QT_FORWARD_DECLARE_CLASS(QDebug)
 
index b1b6fa76455518d9e966b5f1c637283adcbe1da5..a9c61fdd35b2975176e774b657d8c73256fe0ed0 100644 (file)
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfo>
-#include <QtCore/QMetaObject>
-#include <QtCore/QQueue>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QTemporaryFile>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qqueue.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qtemporaryfile.h>
+#include <QtCore/qtextstream.h>
 
 #include <cstdio>
 #include <algorithm>
@@ -166,57 +166,57 @@ const QMultiHash<QString, QString> &AbstractMetaBuilder::typedefTargetToName() c
     return d->m_typedefTargetToName;
 }
 
-void AbstractMetaBuilderPrivate::checkFunctionModifications() const
-{
-    const auto &entries = TypeDatabase::instance()->entries();
-
-    for (auto it = entries.cbegin(), end = entries.cend(); it != end; ++it) {
-        TypeEntryCPtr entry = it.value();
-        if (!entry)
-            continue;
-        if (!entry->isComplex() || !entry->generateCode())
-            continue;
+// Check whether a function modification can be found in a class, else
+// warn with candidates.
+static void checkModification(const FunctionModification &modification,
+                              const AbstractMetaClassPtr &clazz)
 
-        auto centry = std::static_pointer_cast<const ComplexTypeEntry>(entry);
+{
+    const auto &functions = clazz->functions();
 
-        if (!centry->generateCode())
-            continue;
+    auto modificationPredicate = [&clazz, &modification](const AbstractMetaFunctionCPtr &f) {
+        return f->implementingClass() == clazz
+            && modification.matches(f->modificationSignatures());
+    };
 
-        FunctionModificationList modifications = centry->functionModifications();
+    const QString &signature = modification.signature();
+    auto it = std::find_if(functions.cbegin(), functions.cend(), modificationPredicate);
+    if (it != functions.cend()) {
+        if ((*it)->isConstant() && signature.endsWith(u')')) // Warn about missing const
+            qCWarning(lcShiboken, "%s", qPrintable(msgModificationConstMismatch(*it, signature)));
+        return;
+    }
 
-        for (const FunctionModification &modification : std::as_const(modifications)) {
-            QString signature = modification.signature();
+    const auto name = QStringView{signature}.left(signature.indexOf(u'(')).trimmed();
 
-            QString name = signature.trimmed();
-            name.truncate(name.indexOf(u'('));
+    QStringList possibleSignatures;
+    for (const auto &function : functions) {
+        if (!function->isUserAdded() && !function->isUserDeclared()
+            && function->originalName() == name) {
+            possibleSignatures.append(msgModificationCandidates(function));
+        }
+    }
 
-            const auto clazz = AbstractMetaClass::findClass(m_metaClasses, centry);
-            if (!clazz)
-                continue;
+    const QString msg = msgNoFunctionForModification(clazz, signature,
+                                                     modification.originalSignature(),
+                                                     possibleSignatures, clazz->functions());
+    qCWarning(lcShiboken, "%s", qPrintable(msg));
+}
 
-            bool found = false;
-            QStringList possibleSignatures;
-            for (const auto &function : clazz->functions()) {
-                if (function->implementingClass() == clazz
-                    && modification.matches(function->modificationSignatures())) {
-                    found = true;
-                    break;
-                }
+void AbstractMetaBuilderPrivate::checkFunctionModifications() const
+{
+    for (const auto &entry : TypeDatabase::instance()->entries()) {
+        if (!entry->isComplex() || !entry->generateCode())
+            continue;
 
-                if (function->originalName() == name) {
-                    const QString signatures = function->modificationSignatures().join(u'/');
-                    possibleSignatures.append(signatures + u" in "_s
-                                              + function->implementingClass()->name());
-                }
-            }
+        auto centry = std::static_pointer_cast<const ComplexTypeEntry>(entry);
+        const auto clazz = AbstractMetaClass::findClass(m_metaClasses, centry);
+        if (!clazz)
+            continue;
 
-            if (!found) {
-                qCWarning(lcShiboken).noquote().nospace()
-                    << msgNoFunctionForModification(clazz, signature,
-                                                    modification.originalSignature(),
-                                                    possibleSignatures, clazz->functions());
-            }
-        }
+        const FunctionModificationList &modifications = centry->functionModifications();
+        for (const FunctionModification &modification : modifications)
+            checkModification(modification, clazz);
     }
 }
 
@@ -242,7 +242,7 @@ void AbstractMetaBuilderPrivate::registerHashFunction(const FunctionModelItem &f
     if (function_item->isDeleted())
         return;
     ArgumentList arguments = function_item->arguments();
-    if (arguments.size() >= 1) { // (Class, Hash seed).
+    if (!arguments.isEmpty()) { // (Class, Hash seed).
         if (AbstractMetaClassPtr cls = argumentToClass(arguments.at(0), currentClass))
             cls->setHashFunction(function_item->name());
     }
@@ -270,7 +270,7 @@ void AbstractMetaBuilderPrivate::registerToStringCapability(const FunctionModelI
             const ArgumentModelItem &arg = arguments.at(1);
             if (AbstractMetaClassPtr cls = argumentToClass(arg, currentClass)) {
                 if (arg->type().indirections() < 2)
-                    cls->setToStringCapability(true, int(arg->type().indirections()));
+                    cls->setToStringCapability(true, arg->type().indirections());
             }
         }
     }
@@ -585,20 +585,9 @@ void AbstractMetaBuilderPrivate::traverseDom(const FileModelItem &dom,
     }
 
     ReportHandler::startProgress("Checked for inconsistencies in class model.");
-    for (const auto &cls : std::as_const(m_metaClasses)) {
-        AbstractMetaClass::fixFunctions(cls);
-
-        if (cls->canAddDefaultConstructor())
-            AbstractMetaClass::addDefaultConstructor(cls);
-        if (cls->canAddDefaultCopyConstructor())
-            AbstractMetaClass::addDefaultCopyConstructor(cls);
-
-        const bool avoidProtectedHack = flags.testFlag(ApiExtractorFlag::AvoidProtectedHack);
-        const bool vco =
-            AbstractMetaClass::determineValueTypeWithCopyConstructorOnly(cls, avoidProtectedHack);
-        cls->setValueTypeWithCopyConstructorOnly(vco);
-        cls->typeEntry()->setValueTypeWithCopyConstructorOnly(vco);
-    }
+    const bool avoidProtectedHack = flags.testFlag(ApiExtractorFlag::AvoidProtectedHack);
+    for (const auto &cls : std::as_const(m_metaClasses))
+        AbstractMetaClass::fixFunctions(cls, avoidProtectedHack);
 
     const auto &allEntries = types->entries();
 
@@ -787,7 +776,7 @@ AbstractMetaClassPtr
     // Continue populating namespace?
     AbstractMetaClassPtr metaClass = AbstractMetaClass::findClass(m_metaClasses, type);
     if (!metaClass) {
-        metaClass.reset(new AbstractMetaClass);
+        metaClass = std::make_shared<AbstractMetaClass>();
         metaClass->setTypeEntry(type);
         addAbstractMetaClass(metaClass, namespaceItem.get());
         if (auto extendsType = type->extends()) {
@@ -858,8 +847,8 @@ std::optional<AbstractMetaEnum>
     TypeEntryPtr typeEntry;
     const auto enclosingTypeEntry = enclosing ? enclosing->typeEntry() : TypeEntryCPtr{};
     if (enumItem->accessPolicy() == Access::Private) {
-        typeEntry.reset(new EnumTypeEntry(enumItem->qualifiedName().constLast(),
-                                          QVersionNumber(0, 0), enclosingTypeEntry));
+        typeEntry = std::make_shared<EnumTypeEntry>(enumItem->qualifiedName().constLast(),
+                                                    QVersionNumber(0, 0), enclosingTypeEntry);
         TypeDatabase::instance()->addType(typeEntry);
     } else if (enumItem->enumKind() != AnonymousEnum) {
         typeEntry = TypeDatabase::instance()->findType(qualifiedName);
@@ -1069,10 +1058,18 @@ void AbstractMetaBuilderPrivate::traverseTypesystemTypedefs()
             // Synthesize a AbstractMetaType which would be found by an
             // instantiation.
             AbstractMetaType sourceType;
-            sourceType.setTypeEntry(metaClass->templateBaseClass()->typeEntry());
-            sourceType.setInstantiations(metaClass->templateBaseClassInstantiations());
-            sourceType.decideUsagePattern();
-            m_typeSystemTypeDefs.append({sourceType, metaClass});
+            TypeEntryCPtr typeEntry;
+            if (auto templateBase = metaClass->templateBaseClass())
+                typeEntry = templateBase->typeEntry();
+            if (typeEntry) {
+                sourceType.setTypeEntry(typeEntry);
+                sourceType.setInstantiations(metaClass->templateBaseClassInstantiations());
+                sourceType.decideUsagePattern();
+                m_typeSystemTypeDefs.append({sourceType, metaClass});
+            } else {
+                qCWarning(lcShiboken, "Cannot find type entry for source of typedef \"%s\".",
+                          qPrintable(metaClass->qualifiedCppName()));
+            }
         }
     }
 }
@@ -1296,13 +1293,13 @@ std::optional<AbstractMetaField>
     metaField.setEnclosingClass(cls);
 
     TypeInfo fieldType = field->type();
-    auto metaType = translateType(fieldType, cls);
+    auto metaType = translateType(fieldType, cls, {}, &rejectReason);
 
     if (!metaType.has_value()) {
-        const QString type = TypeInfo::resolveType(fieldType, currentScope()).qualifiedName().join(u"::"_s);
         if (cls->typeEntry()->generateCode()) {
-             qCWarning(lcShiboken, "%s",
-                       qPrintable(msgSkippingField(field, cls->name(), type)));
+            const QString signature = qualifiedFieldSignatureWithType(className, field);
+            m_rejectedFields.insert({AbstractMetaBuilder::UnmatchedFieldType,
+                                     signature, signature, rejectReason});
         }
         return {};
     }
@@ -1461,8 +1458,7 @@ void AbstractMetaBuilderPrivate::traverseFunctions(const ScopeModelItem& scopeIt
             && !(metaFunction->isPrivate() && metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction)) {
 
             if (metaFunction->isSignal() && metaClass->hasSignal(metaFunction.get()))
-                qCWarning(lcShiboken, "%s", qPrintable(msgSignalOverloaded(metaClass,
-                                                                           metaFunction.get())));
+                ReportHandler::addGeneralMessage(msgSignalOverloaded(metaClass, metaFunction.get()));
 
             if (metaFunction->isConversionOperator())
                 fixReturnTypeOfConversionOperator(metaFunction);
@@ -1538,7 +1534,7 @@ QStringList AbstractMetaBuilder::definitionNames(const QString &name,
 void AbstractMetaBuilderPrivate::applyFunctionModifications(const AbstractMetaFunctionPtr &func)
 {
     AbstractMetaFunction& funcRef = *func;
-    for (const FunctionModification &mod : func->modifications(func->implementingClass())) {
+    for (const FunctionModification &mod : func->modifications()) {
         if (mod.isRenameModifier()) {
             func->setOriginalName(func->name());
             func->setName(mod.renamedToName());
@@ -1760,7 +1756,7 @@ AbstractMetaFunctionPtr
     }
 
     if (!metaArguments.isEmpty())
-        fixArgumentNames(metaFunction, metaFunction->modifications(metaClass));
+        fixArgumentNames(metaFunction, functionMods);
 
     return metaFunction;
 }
@@ -1814,11 +1810,17 @@ void AbstractMetaBuilderPrivate::fixArgumentNames(const AbstractMetaFunctionPtr
 
 static QString functionSignature(const FunctionModelItem &functionItem)
 {
-    QStringList args;
+    QString result = functionItem->name() + u'(';
     const ArgumentList &arguments = functionItem->arguments();
-    for (const ArgumentModelItem &arg : arguments)
-        args << arg->type().toString();
-    return functionItem->name() + u'(' + args.join(u',') + u')';
+    for (qsizetype i = 0, size = arguments.size(); i < size; ++i) {
+        if (i > 0)
+            result += u',';
+        result += arguments.at(i)->type().toString();
+    }
+    result += u')';
+    if (functionItem->isConstant())
+        result += "const"_L1;
+    return result;
 }
 
 static inline QString qualifiedFunctionSignatureWithType(const FunctionModelItem &functionItem,
@@ -1849,6 +1851,12 @@ static inline AbstractMetaFunction::FunctionType functionTypeFromCodeModel(CodeM
     case CodeModel::AssignmentOperator:
         result = AbstractMetaFunction::AssignmentOperatorFunction;
         break;
+    case CodeModel::MoveAssignmentOperator:
+        result = AbstractMetaFunction::MoveAssignmentOperatorFunction;
+        break;
+    case CodeModel::OtherAssignmentOperator:
+        result = AbstractMetaFunction::OtherAssignmentOperatorFunction;
+        break;
     case CodeModel::CallOperator:
         result = AbstractMetaFunction::CallOperator;
         break;
@@ -1940,7 +1948,7 @@ static bool applyArrayArgumentModifications(const FunctionModificationList &func
 
 // Create the meta type for a view (std::string_view -> std::string)
 static AbstractMetaType createViewOnType(const AbstractMetaType &metaType,
-                                         const TypeEntryCPtr &viewOnTypeEntry)
+                                         const CppTypeEntryCPtr &viewOnTypeEntry)
 {
     auto result = metaType;
     result.setTypeEntry(viewOnTypeEntry);
@@ -2016,6 +2024,15 @@ AbstractMetaFunctionPtr
         case CodeModel::CopyConstructor:
             currentClass->setHasDeletedCopyConstructor(true);
             break;
+        case CodeModel::MoveConstructor:
+            currentClass->setHasDeletedMoveConstructor(true);
+            break;
+        case CodeModel::AssignmentOperator:
+            currentClass->setHasDeletedAssignmentOperator(true);
+            break;
+        case CodeModel::MoveAssignmentOperator:
+            currentClass->setHasDeletedMoveAssignmentOperator(true);
+            break;
         default:
             break;
         }
@@ -2051,15 +2068,28 @@ AbstractMetaFunctionPtr
         return {};
     }
 
-    const QString &signature = functionSignature(functionItem);
-    if (tdb->isFunctionRejected(className, signature, &rejectReason)) {
-        rejectFunction(functionItem, currentClass,
-                       AbstractMetaBuilder::GenerationDisabled, rejectReason);
-        if (ReportHandler::isDebug(ReportHandler::MediumDebug)) {
-            qCInfo(lcShiboken, "%s::%s was rejected by the type database (%s).",
-                   qPrintable(className), qPrintable(signature), qPrintable(rejectReason));
+    QStringList signatures{functionSignature(functionItem)};
+    // FIXME PYSIDE-7: "const" was historically not exactly matched, add a non-const
+    // signature for this to work. Remove in PYSIDE-7
+    if (functionItem->isConstant())
+        signatures.append(signatures.constFirst().left(signatures.constFirst().size() - 5));
+    for (qsizetype i = 0, size = signatures.size(); i < size; ++i) {
+        const QString normalized =
+            QString::fromUtf8(QMetaObject::normalizedSignature(signatures.at(i).toUtf8()));
+        if (normalized != signatures.at(i))
+            signatures.append(normalized);
+    }
+
+    for (const auto &signature : std::as_const(signatures)) {
+        if (tdb->isFunctionRejected(className, signature, &rejectReason)) {
+            rejectFunction(functionItem, currentClass,
+                           AbstractMetaBuilder::GenerationDisabled, rejectReason);
+            if (ReportHandler::isDebug(ReportHandler::MediumDebug)) {
+                qCInfo(lcShiboken, "%s::%s was rejected by the type database (%s).",
+                       qPrintable(className), qPrintable(signature), qPrintable(rejectReason));
+            }
+            return {};
         }
-        return {};
     }
 
     if (functionItem->isFriend())
@@ -2076,10 +2106,7 @@ AbstractMetaFunctionPtr
     AbstractMetaFunction::Flags flags;
     auto metaFunction = std::make_shared<AbstractMetaFunction>(functionName);
     metaFunction->setCppAttributes(cppAttributes);
-    const QByteArray cSignature = signature.toUtf8();
-    const QString unresolvedSignature =
-        QString::fromUtf8(QMetaObject::normalizedSignature(cSignature.constData()));
-    metaFunction->setUnresolvedSignature(unresolvedSignature);
+    metaFunction->setUnresolvedSignatures(signatures);
     if (functionItem->isHiddenFriend())
         flags.setFlag(AbstractMetaFunction::Flag::HiddenFriend);
     metaFunction->setSourceLocation(functionItem->sourceLocation());
@@ -2117,8 +2144,10 @@ AbstractMetaFunctionPtr
         if (!type.has_value()) {
             const QString reason = msgUnmatchedReturnType(functionItem, errorMessage);
             const QString signature = qualifiedFunctionSignatureWithType(functionItem, className);
-            qCWarning(lcShiboken, "%s",
-                      qPrintable(msgSkippingFunction(functionItem, signature, reason)));
+            if (functionItem->attributes().testFlag(FunctionAttribute::Abstract)) { // Potential compilation error
+                qCWarning(lcShiboken, "%s",
+                          qPrintable(msgSkippingFunction(functionItem, signature, reason)));
+            }
             rejectFunction(functionItem, currentClass,
                            AbstractMetaBuilder::UnmatchedReturnType, reason);
             return {};
@@ -2142,8 +2171,7 @@ AbstractMetaFunctionPtr
             arguments.removeLast(); // Strip QT6_DECL_NEW_OVERLOAD_TAIL
             if (!currentClass || currentClass->typeEntry()->generateCode()) {
                 const QString signature = qualifiedFunctionSignatureWithType(functionItem, className);
-                qCWarning(lcShiboken, "%s",
-                          qPrintable(msgStrippingQtDisambiguatedArgument(functionItem, signature)));
+                ReportHandler::addGeneralMessage(msgStrippingQtDisambiguatedArgument(functionItem, signature));
             }
             break;
         }
@@ -2185,8 +2213,10 @@ AbstractMetaFunctionPtr
             }
             const QString reason = msgUnmatchedParameterType(arg, i, errorMessage);
             const QString signature = qualifiedFunctionSignatureWithType(functionItem, className);
-            qCWarning(lcShiboken, "%s",
-                      qPrintable(msgSkippingFunction(functionItem, signature, reason)));
+            if (functionItem->attributes().testFlag(FunctionAttribute::Abstract)) { // Potential compilation error
+                qCWarning(lcShiboken, "%s",
+                          qPrintable(msgSkippingFunction(functionItem, signature, reason)));
+            }
             rejectFunction(functionItem, currentClass,
                            AbstractMetaBuilder::UnmatchedArgumentType, reason);
             return {};
@@ -2195,7 +2225,9 @@ AbstractMetaFunctionPtr
         auto metaType = metaTypeO.value();
         // Add view substitution for simple view types of function arguments
         // std::string_view -> std::string for foo(std::string_view)
-        auto viewOnTypeEntry = metaType.typeEntry()->viewOn();
+        CppTypeEntryCPtr viewOnTypeEntry;
+        if (auto type = std::dynamic_pointer_cast<const CppTypeEntry>(metaType.typeEntry()))
+            viewOnTypeEntry = type->viewOn();
         if (viewOnTypeEntry != nullptr && metaType.indirections() == 0
             && metaType.arrayElementType() == nullptr
             && (!metaType.hasInstantiations() || metaType.isContainer())) {
@@ -2212,7 +2244,7 @@ AbstractMetaFunctionPtr
     AbstractMetaArgumentList &metaArguments = metaFunction->arguments();
 
     const FunctionModificationList functionMods = currentClass
-        ? AbstractMetaFunction::findClassModifications(metaFunction.get(), currentClass)
+        ? AbstractMetaFunction::findMemberModifications(metaFunction.get(), currentClass)
         : AbstractMetaFunction::findGlobalModifications(metaFunction.get());
 
     applyCachedFunctionModifications(metaFunction, functionMods);
@@ -2355,11 +2387,10 @@ TypeEntryCList AbstractMetaBuilderPrivate::findTypeEntries(const QString &qualif
 
     // Resolve entries added by metabuilder (for example, "GLenum") to match
     // the signatures for modifications.
-    for (qsizetype i = 0, size = types.size(); i < size; ++i) {
-        const auto &e = types.at(i);
+    for (auto &e : types) {
         if (e->isPrimitive()) {
             const auto pte = std::static_pointer_cast<const PrimitiveTypeEntry>(e);
-            types[i] = basicReferencedNonBuiltinTypeEntry(pte);
+            e = basicReferencedNonBuiltinTypeEntry(pte);
         }
     }
 
@@ -2410,8 +2441,8 @@ AbstractMetaClassCPtr AbstractMetaBuilderPrivate::resolveTypeSystemTypeDef(const
 
 static void synthesizeWarning(const AbstractMetaFunctionCPtr &f)
 {
-    qCWarning(lcShiboken, "Synthesizing \"%s\"...",
-              qPrintable(f->classQualifiedSignature()));
+    ReportHandler::addGeneralMessage("Synthesizing \""_L1 + f->classQualifiedSignature()
+                                     + "\"..."_L1);
 }
 
 static AbstractMetaFunctionPtr
@@ -2580,8 +2611,8 @@ void AbstractMetaBuilderPrivate::fixSmartPointers()
             fixSmartPointerClass(std::const_pointer_cast<AbstractMetaClass>(smartPointerClass),
                                  ste);
         } else {
-            qCWarning(lcShiboken, "Synthesizing smart pointer \"%s\"...",
-                      qPrintable(ste->qualifiedCppName()));
+            ReportHandler::addGeneralMessage("Synthesizing smart pointer \""_L1
+                                             + ste->qualifiedCppName() + "\"..."_L1);
             m_smartPointers.append(createSmartPointerClass(ste, m_metaClasses));
         }
     }
@@ -2641,7 +2672,7 @@ std::optional<AbstractMetaType>
         qsizetype i = d ? d->m_scopes.size() - 1 : -1;
         while (i >= 0) {
             typeInfo = TypeInfo::resolveType(_typei, d->m_scopes.at(i--));
-            if (typeInfo.qualifiedName().join(u"::"_s) != _typei.qualifiedName().join(u"::"_s))
+            if (typeInfo.qualifiedName() != _typei.qualifiedName())
                 break;
         }
 
@@ -2700,7 +2731,7 @@ std::optional<AbstractMetaType>
             arrayType.setArrayElementType(elementType.value());
             const QString &arrayElement = typeInfo.arrayElements().at(i);
             if (!arrayElement.isEmpty()) {
-                bool _ok;
+                bool _ok{};
                 const qint64 elems = d
                     ? d->findOutValueFromString(arrayElement, _ok)
                     : arrayElement.toLongLong(&_ok, 0);
@@ -2734,7 +2765,7 @@ std::optional<AbstractMetaType>
 
     // 4. Special case QFlags (include instantiation in name)
     if (qualifiedName == u"QFlags") {
-        qualifiedName = typeInfo.toString();
+        qualifiedName = typeInfo.qualifiedInstantationName();
         typeInfo.clearInstantiations();
     }
 
@@ -2773,7 +2804,7 @@ std::optional<AbstractMetaType>
         // For non-type template parameters, create a dummy type entry on the fly
         // as is done for classes.
         if (!targType.has_value()) {
-            const QString value = ti.qualifiedName().join(u"::"_s);
+            const QString value = ti.qualifiedNameString();
             if (isNumber(value)) {
                 auto module = typeSystemTypeEntry(type);
                 TypeDatabase::instance()->addConstantValueTypeEntry(value, module);
@@ -2938,7 +2969,7 @@ QString AbstractMetaBuilderPrivate::fixSimpleDefaultValue(QStringView expr,
     const auto cit = m_classToItem.constFind(klass);
     if (cit == m_classToItem.cend())
         return {};
-    auto *scope = dynamic_cast<const _ScopeModelItem *>(cit.value());
+    const auto *scope = dynamic_cast<const _ScopeModelItem *>(cit.value());
     if (!scope)
         return {};
     if (auto enumValue = scope->findEnumByValue(expr))
@@ -3038,7 +3069,7 @@ QString AbstractMetaBuilder::fixDefaultValue(const QString &expr, const Abstract
 
 bool AbstractMetaBuilderPrivate::isEnum(const FileModelItem &dom, const QStringList& qualified_name)
 {
-    CodeModelItem item = dom->model()->findItem(qualified_name, dom);
+    CodeModelItem item = CodeModel::findItem(qualified_name, dom);
     return item && item->kind() == _EnumModelItem::__node_kind;
 }
 
@@ -3059,7 +3090,7 @@ AbstractMetaClassPtr
         QString prefix = i > 0 ? QStringList(scope.mid(0, i)).join(u"::"_s) + u"::"_s : QString();
         QString completeName = prefix + name;
         const TypeInfo parsed = TypeParser::parse(completeName, &errorMessage);
-        QString qualifiedName = parsed.qualifiedName().join(u"::"_s);
+        QString qualifiedName = parsed.qualifiedNameString();
         if (qualifiedName.isEmpty()) {
             qWarning().noquote().nospace() << "Unable to parse type \"" << completeName
                 << "\" while looking for template \"" << name << "\": " << errorMessage;
@@ -3138,11 +3169,11 @@ std::optional<AbstractMetaType>
 
     if (returned.hasInstantiations()) {
         AbstractMetaTypeList instantiations = returned.instantiations();
-        for (qsizetype i = 0; i < instantiations.size(); ++i) {
-            auto ins = inheritTemplateType(templateTypes, instantiations.at(i));
+        for (auto &instantiation : instantiations) {
+            auto ins = inheritTemplateType(templateTypes, instantiation);
             if (!ins.has_value())
                 return {};
-            instantiations[i] = ins.value();
+            instantiation = ins.value();
         }
         returned.setInstantiations(instantiations);
     }
@@ -3173,7 +3204,7 @@ static std::optional<AbstractMetaType>
                              const AbstractMetaClassCPtr &templateClass,
                              const TypeInfo &info, QString *errorMessage)
 {
-    QString typeName = info.qualifiedName().join("::"_L1);
+    QString typeName = info.qualifiedNameString();
     TypeDatabase *typeDb = TypeDatabase::instance();
     TypeEntryPtr t;
     // Check for a non-type template integer parameter, that is, for a base
@@ -3486,6 +3517,7 @@ static void writeRejectLogFile(const QString &name,
         {AbstractMetaBuilder::RedefinedToNotClass, "Type redefined to not be a class"_ba},
         {AbstractMetaBuilder::UnmatchedReturnType, "Unmatched return type"_ba},
         {AbstractMetaBuilder::UnmatchedArgumentType, "Unmatched argument type"_ba},
+        {AbstractMetaBuilder::UnmatchedFieldType, "Unmatched field type"_ba},
         {AbstractMetaBuilder::UnmatchedOperator, "Unmatched operator"_ba},
         {AbstractMetaBuilder::Deprecated, "Deprecated"_ba}
     };
@@ -3527,7 +3559,7 @@ void AbstractMetaBuilderPrivate::dumpLog() const
 template <class MetaClass>
 static bool addClassDependency(const QList<std::shared_ptr<MetaClass> > &classList,
                                const TypeEntryCPtr &typeEntry,
-                               std::shared_ptr<MetaClass> clazz,
+                               const std::shared_ptr<MetaClass> &clazz,
                                Graph<std::shared_ptr<MetaClass> > *graph)
 {
     if (!typeEntry->isComplex() || typeEntry == clazz->typeEntry())
@@ -3711,7 +3743,7 @@ void AbstractMetaBuilderPrivate::setInclude(const TypeEntryPtr &te, const QStrin
             return;
         }
 
-        int bestMatchLength = 0;
+        qsizetype bestMatchLength = 0;
         for (const auto &headerPath : m_headerPaths) {
             if (headerPath.size() > bestMatchLength && matchHeader(headerPath, path))
                 bestMatchLength = headerPath.size();
index 20261ed3c777ee2e09dda68ad7bb36e90b03f911..67dc82802e8b80e14269aa2c64c4767fe26a567a 100644 (file)
@@ -12,7 +12,7 @@
 
 #include "clangparser/compilersupport.h"
 
-#include <QtCore/QFileInfoList>
+#include <QtCore/qfileinfo.h>
 
 #include <optional>
 
@@ -37,6 +37,7 @@ public:
         RedefinedToNotClass,
         UnmatchedArgumentType,
         UnmatchedReturnType,
+        UnmatchedFieldType,
         UnmatchedOperator,
         Deprecated,
         NoReason
index 4e337339eb29b2694beb14846878ccabb99b875a..45dd21e0260787b52801083d5bc5e7db4b7d7ce8 100644 (file)
 #include "modifications_typedefs.h"
 #include "typesystem_typedefs.h"
 
-#include <QtCore/QFileInfo>
-#include <QtCore/QList>
-#include <QtCore/QMap>
-#include <QtCore/QMultiHash>
-#include <QtCore/QSet>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qset.h>
 
 #include <optional>
 #include <set>
index 8374be1702da8dadb66934385d5227e658b4eda6..486d3d39c34538a05e8d6caf803911614aed5e6f 100644 (file)
@@ -9,7 +9,7 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 #include <algorithm>
 
index 03d7a308296f31fddd96999500cba9de56c7bc80..cfaa9b9bb16f92097a9d61dd05abdcc735858256 100644 (file)
@@ -9,8 +9,8 @@
 #include "parser/codemodel_enums.h"
 #include "typesystem_typedefs.h"
 
-#include <QtCore/QSharedDataPointer>
-#include <QtCore/QString>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qstring.h>
 
 #include <optional>
 
index b4385228c526e9738ebf92885dc2cc733ba36a4e..a541dd31845ea430c4779d5f34747145d7dc9f00 100644 (file)
@@ -13,7 +13,7 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 #include <algorithm>
 
index 0fa8587915d5ce8f688c4f49e3bf9d2849ad13c9..9237c35335d6a28d6fcd54532b94704e381bdb82 100644 (file)
@@ -11,7 +11,7 @@
 #include "typesystem_typedefs.h"
 #include "enclosingclassmixin.h"
 
-#include <QtCore/QSharedDataPointer>
+#include <QtCore/qshareddata.h>
 
 #include <optional>
 
index 5198d69a472be5a2baebbbd73e67cf6478e80a49..486a8b30f62c889d2b2f8e90f62286eb8c3b698e 100644 (file)
@@ -25,8 +25,8 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QRegularExpression>
+#include <QtCore/qdebug.h>
+#include <QtCore/qregularexpression.h>
 
 #include <algorithm>
 
@@ -59,8 +59,9 @@ public:
     QString modifiedName(const AbstractMetaFunction *q) const;
     int overloadNumber(const AbstractMetaFunction *q) const;
 
-    const FunctionModificationList &modifications(const AbstractMetaFunction *q,
-                                                  const AbstractMetaClassCPtr &implementor) const;
+    const FunctionModificationList &globalModifications(const AbstractMetaFunction *q) const;
+    const FunctionModificationList &memberModifications(const AbstractMetaFunction *q,
+                                                        const AbstractMetaClassCPtr &implementor) const;
 
     bool applyTypeModification(const AbstractMetaFunction *q,
                                const QString &type, int number, QString *errorMessage);
@@ -71,7 +72,7 @@ public:
     mutable QString m_cachedMinimalSignature;
     mutable QString m_cachedSignature;
     mutable QString m_cachedModifiedName;
-    QString m_unresolvedSignature;
+    QStringList m_unresolvedSignatures;
 
     FunctionTypeEntryPtr m_typeEntry;
     AbstractMetaFunction::FunctionType m_functionType = AbstractMetaFunction::NormalFunction;
@@ -80,6 +81,7 @@ public:
     AbstractMetaClassCPtr m_class;
     AbstractMetaClassCPtr m_implementingClass;
     AbstractMetaClassCPtr m_declaringClass;
+    AbstractMetaFunctionCPtr m_overridden; /// overridden base function of a virtual
     mutable ModificationCache m_modificationCache;
     int m_propertySpecIndex = -1;
     AbstractMetaArgumentList m_arguments;
@@ -224,9 +226,8 @@ void AbstractMetaFunction::setExplicit(bool isExplicit)
 
 bool AbstractMetaFunction::returnsBool() const
 {
-    if (d->m_type.typeUsagePattern() != AbstractMetaType::PrimitivePattern)
-        return false;
-    return basicReferencedTypeEntry(d->m_type.typeEntry())->name() == u"bool";
+    return d->m_type.typeUsagePattern() == AbstractMetaType::PrimitivePattern
+        && d->m_type.basicPrimitiveName() == "bool"_L1;
 }
 
 bool AbstractMetaFunction::isOperatorBool() const
@@ -338,12 +339,6 @@ void AbstractMetaFunction::setOwnerClass(const AbstractMetaClassCPtr &cls)
     d->m_class = cls;
 }
 
-bool AbstractMetaFunction::operator<(const AbstractMetaFunction &other) const
-{
-    return compareTo(&other) & NameLessThan;
-}
-
-
 /*!
     Returns a mask of CompareResult describing how this function is
     compares to another function
@@ -352,65 +347,44 @@ AbstractMetaFunction::CompareResult AbstractMetaFunction::compareTo(const Abstra
 {
     CompareResult result;
 
-    // Enclosing class...
-    if (ownerClass() == other->ownerClass())
-        result |= EqualImplementor;
-
-    // Attributes
-    if (attributes() == other->attributes() && cppAttributes() == other->cppAttributes())
-        result |= EqualAttributes;
-
     // Compare types
-    if (type().name() == other->type().name())
-        result |= EqualReturnType;
+    if (type() == other->type())
+        result.setFlag(EqualReturnType);
 
     // Compare names
-    int cmp = originalName().compare(other->originalName());
+    if (originalName() == other->originalName())
+        result.setFlag(EqualName);
+
+    if (isStatic() == other->isStatic())
+        result.setFlag(EqualStatic);
 
-    if (cmp < 0)
-        result |= NameLessThan;
-    else if (!cmp)
-        result |= EqualName;
+    if (isConstant() == other->isConstant())
+        result.setFlag(EqualConst);
+
+    if (isVirtual()  == other->isVirtual())
+        result.setFlag(EqualVirtual);
 
     // compare name after modification...
-    cmp = modifiedName().compare(other->modifiedName());
-    if (!cmp)
-        result |= EqualModifiedName;
+    if (modifiedName() == other->modifiedName())
+        result.setFlag(EqualModifiedName);
 
     // Compare arguments...
-    AbstractMetaArgumentList minArguments;
-    AbstractMetaArgumentList maxArguments;
-    if (arguments().size() < other->arguments().size()) {
-        minArguments = arguments();
-        maxArguments = other->arguments();
-    } else {
-        minArguments = other->arguments();
-        maxArguments = arguments();
-    }
-
-    const auto minCount = minArguments.size();
-    const auto maxCount = maxArguments.size();
-    bool same = true;
-    for (qsizetype i = 0; i < maxCount; ++i) {
-        if (i < minCount) {
-            const AbstractMetaArgument &min_arg = minArguments.at(i);
-            const AbstractMetaArgument &max_arg = maxArguments.at(i);
-            if (min_arg.type().name() != max_arg.type().name()
-                && (min_arg.defaultValueExpression().isEmpty() || max_arg.defaultValueExpression().isEmpty())) {
-                same = false;
-                break;
-            }
-        } else {
-            if (maxArguments.at(i).defaultValueExpression().isEmpty()) {
-                same = false;
-                break;
+    const auto argumentCount = d->m_arguments.size();
+    const auto &otherArguments = other->arguments();
+    if (argumentCount == otherArguments.size()) {
+        result.setFlag(EqualArgumentCount);
+        bool equals = true;
+        for (qsizetype a = 0; a < argumentCount; ++a) {
+            if ((d->m_arguments.at(a).type() != otherArguments.at(a).type())) {
+                equals = false;
+                if (a < 4)
+                    result.setFlag(CompareResultFlag(DifferArgument1 << a));
             }
         }
+        if (equals)
+            result.setFlag(EqualArguments);
     }
 
-    if (same)
-        result |= minCount == maxCount ? EqualArguments : EqualDefaultValueOverload;
-
     return result;
 }
 
@@ -438,6 +412,16 @@ bool AbstractMetaFunction::isConstOverloadOf(const AbstractMetaFunction *other)
     return true;
 }
 
+AbstractMetaFunctionCPtr AbstractMetaFunction::overridden() const
+{
+    return d->m_overridden;
+}
+
+void AbstractMetaFunction::setOverriddden(const AbstractMetaFunctionCPtr &o)
+{
+    d->m_overridden = o;
+}
+
 AbstractMetaFunction *AbstractMetaFunction::copy() const
 {
     auto *cpy = new AbstractMetaFunction;
@@ -489,6 +473,7 @@ bool AbstractMetaFunction::generateBinding() const
     case AssignmentOperatorFunction:
     case MoveAssignmentOperatorFunction:
     case AbstractMetaFunction::MoveConstructorFunction:
+    case OtherAssignmentOperatorFunction:
         return false;
     default:
         if (!isWhiteListed())
@@ -569,14 +554,14 @@ QString AbstractMetaFunction::classQualifiedSignature() const
     return result;
 }
 
-QString AbstractMetaFunction::unresolvedSignature() const
+QStringList AbstractMetaFunction::unresolvedSignatures() const
 {
-    return d->m_unresolvedSignature;
+    return d->m_unresolvedSignatures;
 }
 
-void AbstractMetaFunction::setUnresolvedSignature(const QString &s)
+void AbstractMetaFunction::setUnresolvedSignatures(const QStringList &s)
 {
-    d->m_unresolvedSignature = s;
+    d->m_unresolvedSignatures = s;
 }
 
 bool AbstractMetaFunction::isConstant() const
@@ -1039,8 +1024,10 @@ QString AbstractMetaFunction::minimalSignature() const
 QStringList AbstractMetaFunction::modificationSignatures() const
 {
     QStringList result{minimalSignature()};
-    if (d->m_unresolvedSignature != result.constFirst())
-        result.append(d->m_unresolvedSignature);
+    for (const auto &signature : std::as_const(d->m_unresolvedSignatures)) {
+        if (signature != result.constFirst())
+            result.append(signature);
+    }
     return result;
 }
 
@@ -1067,19 +1054,30 @@ QString AbstractMetaFunction::debugSignature() const
     return result;
 }
 
-FunctionModificationList AbstractMetaFunction::findClassModifications(const AbstractMetaFunction *f,
-                                                                      AbstractMetaClassCPtr implementor)
+static void findModificationRecursion(const QStringList &signatures,
+                                      const AbstractMetaClassCPtr &implementor,
+                                      const AbstractMetaClassCPtr &stopAt,
+                                      unsigned level, FunctionModificationList *result)
+{
+    auto mods = implementor->typeEntry()->functionModifications(signatures);
+    for (auto &mod : mods)
+        mod.setInherited(level != 0);
+    result->append(mods);
+    if (implementor->inheritanceDone() // AbstractMetaBuilder phase?
+        && (implementor != stopAt || mods.isEmpty())) {
+        level += 1;
+        for (const auto &base : implementor->baseClasses())
+            findModificationRecursion(signatures, base, stopAt, level, result);
+    }
+}
+
+FunctionModificationList
+AbstractMetaFunction::findMemberModifications(const AbstractMetaFunction *f,
+                                              const AbstractMetaClassCPtr &implementor)
 {
     const auto signatures = f->modificationSignatures();
     FunctionModificationList mods;
-    while (implementor) {
-        mods += implementor->typeEntry()->functionModifications(signatures);
-        if ((implementor == implementor->baseClass()) ||
-            (implementor == f->implementingClass() && !mods.isEmpty())) {
-                break;
-        }
-        implementor = implementor->baseClass();
-    }
+    findModificationRecursion(signatures, implementor, f->implementingClass(), 0, &mods);
     return mods;
 }
 
@@ -1090,29 +1088,42 @@ FunctionModificationList AbstractMetaFunction::findGlobalModifications(const Abs
 }
 
 const FunctionModificationList &
-    AbstractMetaFunctionPrivate::modifications(const AbstractMetaFunction *q,
-                                               const AbstractMetaClassCPtr &implementor) const
+    AbstractMetaFunctionPrivate::globalModifications(const AbstractMetaFunction *q) const
 {
+    if (m_addedFunction)
+        return m_addedFunction->modifications();
+    if (m_modificationCache.isEmpty())
+        m_modificationCache.append({{}, AbstractMetaFunction::findGlobalModifications(q)});
+    return m_modificationCache.constFirst().modifications;
+}
+
+const FunctionModificationList &
+    AbstractMetaFunctionPrivate::memberModifications(const AbstractMetaFunction *q,
+                                                     const AbstractMetaClassCPtr &implementor) const
+{
+    Q_ASSERT(implementor);
     if (m_addedFunction)
         return m_addedFunction->modifications();
     for (const auto &ce : m_modificationCache) {
         if (ce.klass == implementor)
             return ce.modifications;
     }
-    auto modifications = m_class == nullptr
-        ? AbstractMetaFunction::findGlobalModifications(q)
-        : AbstractMetaFunction::findClassModifications(q, implementor);
 
+    auto modifications = AbstractMetaFunction::findMemberModifications(q, implementor);
     m_modificationCache.append({implementor, modifications});
     return m_modificationCache.constLast().modifications;
 }
 
 const FunctionModificationList &
-    AbstractMetaFunction::modifications(AbstractMetaClassCPtr implementor) const
+    AbstractMetaFunction::modifications(const AbstractMetaClassCPtr &implementor) const
 {
-    if (!implementor)
-        implementor = d->m_class;
-    return d->modifications(this, implementor);
+    // Note: m_class might be null here in early stages of AbstractMetaBuilder.
+    // Fully rely on implementor, then.
+    if (implementor)
+        return d->memberModifications(this, implementor);
+    if (d->m_class)
+        return d->memberModifications(this, d->m_class);
+    return d->globalModifications(this);
 }
 
 void AbstractMetaFunction::clearModificationsCache()
@@ -1670,8 +1681,8 @@ void AbstractMetaFunction::formatDebugVerbose(QDebug &debug) const
     }
     const QString signature = minimalSignature();
     debug << "), signature=\"" << signature << '"';
-    if (signature != d->m_unresolvedSignature)
-        debug << ", unresolvedSignature=\"" << d->m_unresolvedSignature << '"';
+    if (!d->m_unresolvedSignatures.isEmpty())
+        debug << ", unresolvedSignatures=\"" << d->m_unresolvedSignatures << '"';
     if (d->m_constant)
         debug << " [const]";
     if (d->m_reverse)
index 3bc38ab2a7ca00696aee579a540790f283528ddf..6b36ed49629734b4149cf6085b8afcf6aae2d626 100644 (file)
@@ -11,8 +11,8 @@
 #include "typesystem_typedefs.h"
 #include "parser/codemodel_enums.h"
 
-#include <QtCore/QMetaObject>
-#include <QtCore/QScopedPointer>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qscopedpointer.h>
 
 #include <optional>
 
@@ -40,6 +40,7 @@ public:
         MoveConstructorFunction,
         AssignmentOperatorFunction,
         MoveAssignmentOperatorFunction,
+        OtherAssignmentOperatorFunction, // Assign from some other type
         DestructorFunction,
         NormalFunction,
         SignalFunction,
@@ -72,19 +73,20 @@ public:
     Q_ENUM(ComparisonOperatorType)
 
     enum CompareResultFlag {
-        EqualName                   = 0x00000001,
-        EqualArguments              = 0x00000002,
-        EqualAttributes             = 0x00000004,
-        EqualImplementor            = 0x00000008,
-        EqualReturnType             = 0x00000010,
-        EqualDefaultValueOverload   = 0x00000020,
-        EqualModifiedName           = 0x00000040,
-
-        NameLessThan                = 0x00001000,
-
-        PrettySimilar               = EqualName | EqualArguments,
-        Equal                       = 0x0000001f,
-        NotEqual                    = 0x00001000
+        EqualName            = 0x0001,
+        EqualModifiedName    = 0x0002,
+        EqualVirtual         = 0x0004,
+        EqualStatic          = 0x0008,
+        EqualConst           = 0x0010,
+        EqualReturnType      = 0x0020,
+        EqualArgumentCount   = 0x0040,
+        EqualArguments       = 0x0080,
+        DifferArgument1      = 0x0100, // Argument 1 is different
+        DifferArgument2      = 0x0200,
+        DifferArgument3      = 0x0400,
+        DifferArgument4      = 0x0800,
+        Differ4ArgumentsMask = 0x0f00,
+        EqualAll             = 0xffff
     };
     Q_DECLARE_FLAGS(CompareResult, CompareResultFlag)
     Q_FLAG(CompareResultFlag)
@@ -125,7 +127,8 @@ public:
         OperatorClassArgumentByValue = 0x4, // The removed class argument was passed by value
         InheritedFromTemplate = 0x8, // Inherited from a template in metabuilder
         HiddenFriend = 0x10,
-        PrivateSignal = 0x20 // Private Qt signal (cannot emit from client code)
+        PrivateSignal = 0x20, // Private Qt signal (cannot emit from client code)
+        CovariantReturn = 0x40 // Return type of virtual function differs (eg clone())
     };
     Q_DECLARE_FLAGS(Flags, Flag)
 
@@ -293,8 +296,8 @@ public:
     QString classQualifiedSignature() const;
 
     /// Signature with unresolved typedefs as seen by the code parser
-    QString unresolvedSignature() const;
-    void setUnresolvedSignature(const QString &);
+    QStringList unresolvedSignatures() const;
+    void setUnresolvedSignatures(const QStringList &);
 
     bool isConstant() const;
     void setConstant(bool constant);
@@ -309,8 +312,6 @@ public:
     CompareResult compareTo(const AbstractMetaFunction *other) const;
     bool isConstOverloadOf(const AbstractMetaFunction *other) const;
 
-    bool operator <(const AbstractMetaFunction &a) const;
-
     AbstractMetaFunction *copy() const;
 
     QString conversionRule(TypeSystem::Language language, int idx) const;
@@ -361,13 +362,15 @@ public:
     */
     bool hasSignatureModifications() const;
 
-    const FunctionModificationList &modifications(AbstractMetaClassCPtr implementor = {}) const;
+    const FunctionModificationList &
+        modifications(const AbstractMetaClassCPtr &implementor = {}) const;
     void clearModificationsCache();
 
     DocModificationList addedFunctionDocModifications() const;
 
-    static FunctionModificationList findClassModifications(const AbstractMetaFunction *f,
-                                                           AbstractMetaClassCPtr implementor);
+    // For AbstractMetaBuilder only.
+    static FunctionModificationList findMemberModifications(const AbstractMetaFunction *f,
+                                                            const AbstractMetaClassCPtr &implementor);
     static FunctionModificationList findGlobalModifications(const AbstractMetaFunction *f);
 
     /**
@@ -433,6 +436,10 @@ public:
     SourceLocation sourceLocation() const;
     void setSourceLocation(const SourceLocation &sourceLocation);
 
+    /// For virtual functions, return the overridden base class function
+    AbstractMetaFunctionCPtr overridden() const;
+    void setOverriddden(const AbstractMetaFunctionCPtr &o);
+
     static const char *pythonRichCompareOpCode(ComparisonOperatorType ct);
     static const char *cppComparisonOperator(ComparisonOperatorType ct);
 
index 536cf7b78078747ef494b21afd5c1585eefc2812..e88f354bbd17df81a06681480f7f961d1f6f934a 100644 (file)
@@ -23,7 +23,7 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 #include <algorithm>
 
@@ -45,6 +45,11 @@ public:
           m_hasPrivateConstructor(false),
           m_hasDeletedDefaultConstructor(false),
           m_hasDeletedCopyConstructor(false),
+          m_hasDeletedMoveConstructor(false),
+          m_hasDeletedAssigmentOperator(false),
+          m_hasDeletedMoveAssigmentOperator(false),
+          m_isDefaultConstructible(false),
+          m_isCopyConstructible(false),
           m_functionsFixed(false),
           m_inheritanceDone(false),
           m_hasPrivateDestructor(false),
@@ -52,7 +57,6 @@ public:
           m_hasVirtualDestructor(false),
           m_isTypeDef(false),
           m_hasToStringCapability(false),
-          m_valueTypeWithCopyConstructorOnly(false),
           m_hasCachedWrapper(false)
     {
     }
@@ -75,6 +79,9 @@ public:
     bool hasConstructors() const;
     qsizetype indexOfProperty(const QString &name) const;
 
+    bool isImplicitlyDefaultConstructible() const;
+    bool isImplicitlyCopyConstructible() const;
+
     uint m_hasVirtuals : 1;
     uint m_isPolymorphic : 1;
     uint m_hasNonpublic : 1;
@@ -82,6 +89,11 @@ public:
     uint m_hasPrivateConstructor : 1;
     uint m_hasDeletedDefaultConstructor : 1;
     uint m_hasDeletedCopyConstructor : 1;
+    uint m_hasDeletedMoveConstructor : 1;
+    uint m_hasDeletedAssigmentOperator : 1;
+    uint m_hasDeletedMoveAssigmentOperator : 1;
+    uint m_isDefaultConstructible : 1;
+    uint m_isCopyConstructible : 1;
     uint m_functionsFixed : 1;
     uint m_inheritanceDone : 1; // m_baseClasses has been populated from m_baseClassNames
     uint m_hasPrivateDestructor : 1;
@@ -89,7 +101,6 @@ public:
     uint m_hasVirtualDestructor : 1;
     uint m_isTypeDef : 1;
     uint m_hasToStringCapability : 1;
-    uint m_valueTypeWithCopyConstructorOnly : 1;
     mutable uint m_hasCachedWrapper : 1;
 
     Documentation m_doc;
@@ -620,11 +631,8 @@ AbstractMetaFunctionCPtr AbstractMetaClass::findQtIsNullMethod() const
 
 bool AbstractMetaClass::hasProtectedFields() const
 {
-    for (const AbstractMetaField &field : d->m_fields) {
-        if (field.isProtected())
-            return true;
-    }
-    return false;
+    return std::any_of(d->m_fields.cbegin(), d->m_fields.cend(),
+                       [](const AbstractMetaField &f) { return f.isProtected(); });
 }
 
 const TypeEntryCList &AbstractMetaClass::templateArguments() const
@@ -801,11 +809,7 @@ bool AbstractMetaClass::hasConstructors() const
 
 AbstractMetaFunctionCPtr AbstractMetaClass::copyConstructor() const
 {
-    for (const auto &f : d->m_functions) {
-        if (f->functionType() == AbstractMetaFunction::CopyConstructorFunction)
-            return f;
-    }
-    return {};
+    return queryFirstFunction(d->m_functions, FunctionQueryOption::CopyConstructor);
 }
 
 bool AbstractMetaClass::hasCopyConstructor() const
@@ -819,6 +823,42 @@ bool AbstractMetaClass::hasPrivateCopyConstructor() const
     return copyCt && copyCt->isPrivate();
 }
 
+AbstractMetaFunctionCPtr AbstractMetaClass::moveConstructor() const
+{
+    return queryFirstFunction(d->m_functions, FunctionQueryOption::MoveConstructor);
+}
+
+bool AbstractMetaClass::hasMoveConstructor() const
+{
+    return moveConstructor() != nullptr;
+}
+
+bool AbstractMetaClass::hasPrivateMoveConstructor() const
+{
+    const auto moveCt = moveConstructor();
+    return moveCt && moveCt->isPrivate();
+}
+
+AbstractMetaFunctionCPtr AbstractMetaClass::assignmentOperator() const
+{
+    return queryFirstFunction(d->m_functions, FunctionQueryOption::AssignmentOperator);
+}
+
+bool AbstractMetaClass::hasAssignmentOperator() const
+{
+    return assignmentOperator() != nullptr;
+}
+
+AbstractMetaFunctionCPtr AbstractMetaClass::moveAssignmentOperator() const
+{
+     return queryFirstFunction(d->m_functions, FunctionQueryOption::MoveAssignmentOperator);
+}
+
+bool AbstractMetaClass::hasMoveAssignmentOperator() const
+{
+    return moveAssignmentOperator() != nullptr;
+}
+
 void AbstractMetaClassPrivate::addConstructor(AbstractMetaFunction::FunctionType t,
                                               Access access,
                                               const AbstractMetaArgumentList &arguments,
@@ -947,6 +987,36 @@ void AbstractMetaClass::setHasDeletedCopyConstructor(bool value)
     d->m_hasDeletedCopyConstructor = value;
 }
 
+bool AbstractMetaClass::hasDeletedMoveConstructor() const
+{
+    return d->m_hasDeletedMoveConstructor;
+}
+
+void AbstractMetaClass::setHasDeletedMoveConstructor(bool value)
+{
+    d->m_hasDeletedMoveConstructor = value;
+}
+
+bool AbstractMetaClass::hasDeletedAssignmentOperator() const
+{
+    return d->m_hasDeletedAssigmentOperator;
+}
+
+void AbstractMetaClass::setHasDeletedAssignmentOperator(bool value)
+{
+    d->m_hasDeletedAssigmentOperator = value;
+}
+
+bool AbstractMetaClass::hasDeletedMoveAssignmentOperator() const
+{
+    return d->m_hasDeletedMoveAssigmentOperator;
+}
+
+void AbstractMetaClass::setHasDeletedMoveAssignmentOperator(bool value)
+{
+    d->m_hasDeletedMoveAssigmentOperator = value;
+}
+
 bool AbstractMetaClass::hasPrivateDestructor() const
 {
     return d->m_hasPrivateDestructor;
@@ -981,16 +1051,7 @@ void AbstractMetaClass::setHasVirtualDestructor(bool value)
 
 bool AbstractMetaClass::isDefaultConstructible() const
 {
-    // Private constructors are skipped by the builder.
-    if (hasDeletedDefaultConstructor() || hasPrivateConstructor())
-        return false;
-    const AbstractMetaFunctionCList ctors =
-        queryFunctions(FunctionQueryOption::Constructors);
-    for (const auto &ct : ctors) {
-        if (ct->isDefaultConstructor())
-            return ct->isPublic();
-    }
-    return ctors.isEmpty() && isImplicitlyDefaultConstructible();
+    return d->m_isDefaultConstructible;
 }
 
 // Non-comprehensive check for default constructible field
@@ -1004,46 +1065,35 @@ static bool defaultConstructibleField(const AbstractMetaField &f)
         && !(type.indirections() == 0 && type.isConstant()); // no const values
 }
 
-bool AbstractMetaClass::isImplicitlyDefaultConstructible() const
+bool AbstractMetaClassPrivate::isImplicitlyDefaultConstructible() const
 {
-    return std::all_of(d->m_fields.cbegin(), d->m_fields.cend(),
-                        defaultConstructibleField)
-        && std::all_of(d->m_baseClasses.cbegin(), d->m_baseClasses.cend(),
+    return std::all_of(m_fields.cbegin(), m_fields.cend(),
+                       defaultConstructibleField)
+        && std::all_of(m_baseClasses.cbegin(), m_baseClasses.cend(),
                        [] (const AbstractMetaClassCPtr &c) {
                            return c->isDefaultConstructible();
                        });
 }
 
-static bool canAddDefaultConstructorHelper(const AbstractMetaClass *cls)
-{
-    return !cls->isNamespace()
-        && !cls->hasDeletedDefaultConstructor()
-        && !cls->attributes().testFlag(AbstractMetaClass::HasRejectedConstructor)
-        && !cls->hasPrivateDestructor();
-}
-
 bool AbstractMetaClass::canAddDefaultConstructor() const
 {
-    return canAddDefaultConstructorHelper(this) && !hasConstructors()
-        && !hasPrivateConstructor() && isImplicitlyDefaultConstructible();
+    return !isNamespace()
+        && !hasDeletedDefaultConstructor()
+        && !attributes().testFlag(AbstractMetaClass::HasRejectedConstructor)
+        && !hasPrivateDestructor()
+        && !hasConstructors()
+        && !hasPrivateConstructor() && d->isImplicitlyDefaultConstructible();
 }
 
 bool AbstractMetaClass::isCopyConstructible() const
 {
-    // Private constructors are skipped by the builder.
-    if (hasDeletedCopyConstructor() || hasPrivateCopyConstructor())
-        return false;
-    const AbstractMetaFunctionCList copyCtors =
-        queryFunctions(FunctionQueryOption::CopyConstructor);
-    return copyCtors.isEmpty()
-        ? isImplicitlyCopyConstructible()
-        : copyCtors.constFirst()->isPublic();
+    return d->m_isCopyConstructible;
 }
 
-bool AbstractMetaClass::isImplicitlyCopyConstructible() const
+bool AbstractMetaClassPrivate::isImplicitlyCopyConstructible() const
 {
     // Fields are currently not considered
-    return std::all_of(d->m_baseClasses.cbegin(), d->m_baseClasses.cend(),
+    return std::all_of(m_baseClasses.cbegin(), m_baseClasses.cend(),
                        [] (const AbstractMetaClassCPtr &c) {
                            return c->isCopyConstructible();
                        });
@@ -1051,12 +1101,15 @@ bool AbstractMetaClass::isImplicitlyCopyConstructible() const
 
 bool AbstractMetaClass::canAddDefaultCopyConstructor() const
 {
-    if (!canAddDefaultConstructorHelper(this)
-        || !d->m_typeEntry->isValue() || isAbstract()
-        || hasPrivateCopyConstructor() || hasCopyConstructor()) {
-        return false;
-    }
-    return isImplicitlyCopyConstructible();
+    return d->m_typeEntry->isValue()
+        && !isNamespace()
+        && !hasDeletedCopyConstructor() && !hasCopyConstructor()
+        && !hasDeletedAssignmentOperator() && !hasAssignmentOperator()
+        && !hasDeletedMoveConstructor() && !hasMoveConstructor()
+        && !hasDeletedMoveAssignmentOperator() && !hasMoveAssignmentOperator()
+        && !hasPrivateDestructor()
+        && !isAbstract()
+        && d->isImplicitlyCopyConstructible();
 }
 
 static bool classHasParentManagement(const AbstractMetaClassCPtr &c)
@@ -1206,11 +1259,36 @@ bool AbstractMetaClass::queryFunction(const AbstractMetaFunction *f, FunctionQue
         return false;
     }
 
+    if (query.testFlag(FunctionQueryOption::DefaultConstructor)
+        && (f->functionType() != AbstractMetaFunction::ConstructorFunction
+            || !f->isDefaultConstructor()
+            || f->ownerClass() != f->implementingClass())) {
+        return false;
+    }
+
     if (query.testFlag(FunctionQueryOption::CopyConstructor)
         && (!f->isCopyConstructor() || f->ownerClass() != f->implementingClass())) {
         return false;
     }
 
+    if (query.testFlag(FunctionQueryOption::MoveConstructor)
+        && (f->functionType() != AbstractMetaFunction::MoveConstructorFunction
+            || f->ownerClass() != f->implementingClass())) {
+        return false;
+    }
+
+    if (query.testFlag(FunctionQueryOption::AssignmentOperator)
+        && (f->functionType() != AbstractMetaFunction::AssignmentOperatorFunction
+            || f->ownerClass() != f->implementingClass())) {
+        return false;
+    }
+
+    if (query.testFlag(FunctionQueryOption::MoveAssignmentOperator)
+        && (f->functionType() != AbstractMetaFunction::MoveAssignmentOperatorFunction
+            || f->ownerClass() != f->implementingClass())) {
+        return false;
+    }
+
     // Destructors are never included in the functions of a class currently
     /*
            if ((query & Destructors) && (!f->isDestructor()
@@ -1406,22 +1484,6 @@ static void addExtraIncludesForFunction(const AbstractMetaClassPtr &metaClass,
     }
 }
 
-static bool addSuperFunction(const AbstractMetaFunctionCPtr &f)
-{
-    switch (f->functionType()) {
-    case AbstractMetaFunction::ConstructorFunction:
-    case AbstractMetaFunction::CopyConstructorFunction:
-    case AbstractMetaFunction::MoveConstructorFunction:
-    case AbstractMetaFunction::AssignmentOperatorFunction:
-    case AbstractMetaFunction::MoveAssignmentOperatorFunction:
-    case AbstractMetaFunction::DestructorFunction:
-        return false;
-    default:
-        break;
-    }
-    return true;
-}
-
 // Add constructors imported via "using" from the base classes. This is not
 // needed for normal hidden inherited member functions since we generate a
 // cast to the base class to call them into binding code.
@@ -1454,7 +1516,7 @@ static inline bool isSignal(const AbstractMetaFunctionCPtr &f)
     return f->isSignal();
 }
 
-void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass)
+void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass, bool avoidProtectedHack)
 {
     auto *d = klass->d.data();
     if (d->m_functionsFixed)
@@ -1484,7 +1546,7 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass)
         }
 
         auto superClass = std::const_pointer_cast<AbstractMetaClass>(superClassC);
-        AbstractMetaClass::fixFunctions(superClass);
+        AbstractMetaClass::fixFunctions(superClass, avoidProtectedHack);
         // Since we always traverse the complete hierarchy we are only
         // interrested in what each super class implements, not what
         // we may have propagated from their base classes again.
@@ -1497,6 +1559,10 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass)
         const auto virtuals = superClass->queryFunctions(FunctionQueryOption::VirtualInCppFunctions);
         superFuncs += virtuals;
 
+        // Loop over super functions, comparing them to the class functions to
+        // find cases of function hiding by name. Virtual super functions that
+        // are not reimplemented are cloned into the class for the Python
+        // override code to be generated.
         QSet<AbstractMetaFunctionCPtr> funcsToAdd;
         for (const auto &sf : std::as_const(superFuncs)) {
             if (sf->isModifiedRemoved())
@@ -1513,21 +1579,30 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass)
 
             // we generally don't care about private functions, but we have to get the ones that are
             // virtual in case they override abstract functions.
-            bool add = addSuperFunction(sf);
+            const bool superIsVirtual = sf->isVirtual();
+            bool add = superIsVirtual && !sf->isDestructor();
             for (const auto &cf : std::as_const(nonRemovedFuncs)) {
+                const bool isVirtual = cf->isVirtual();
                 AbstractMetaFunctionPtr f(std::const_pointer_cast<AbstractMetaFunction>(cf));
                 const AbstractMetaFunction::CompareResult cmp = cf->compareTo(sf.get());
 
                 if (cmp & AbstractMetaFunction::EqualModifiedName) {
                     add = false;
                     if (cmp & AbstractMetaFunction::EqualArguments) {
-                        // Set "override" in case it was not spelled out (since it
-                        // is then not detected by clang parsing).
                         const auto attributes = cf->cppAttributes();
-                        if (attributes.testFlag(FunctionAttribute::Virtual)
-                            && !attributes.testFlag(FunctionAttribute::Override)
-                            && !attributes.testFlag(FunctionAttribute::Final)) {
-                            f->setCppAttribute(FunctionAttribute::Override);
+                        if (superIsVirtual && isVirtual) {
+                            f->setOverriddden(sf);
+                            auto flags = f->flags();
+                            if (!flags.testFlag(AbstractMetaFunction::Flag::CovariantReturn)
+                                && f->type() != sf->type()) {
+                                f->setFlags(flags | AbstractMetaFunction::Flag::CovariantReturn);
+                            }
+                            // Set "override" in case it was not spelled out (since it
+                            // is then not detected by clang parsing).
+                            if (!attributes.testFlag(FunctionAttribute::Override)
+                                && !attributes.testFlag(FunctionAttribute::Final)) {
+                                f->setCppAttribute(FunctionAttribute::Override);
+                            }
                         }
 
                         if (f->access() != sf->access()) {
@@ -1549,21 +1624,10 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass)
                         }
 
                         // Set the class which first declares this function, afawk
-                        f->setDeclaringClass(sf->declaringClass());
-                    }
-
-                    if (cmp & AbstractMetaFunction::EqualDefaultValueOverload) {
-                        AbstractMetaArgumentList arguments;
-                        if (f->arguments().size() < sf->arguments().size())
-                            arguments = sf->arguments();
-                        else
-                            arguments = f->arguments();
-                        //TODO: fix this
-                        //for (int i=0; i<arguments.size(); ++i)
-                        //    arguments[i]->setDefaultValueExpression("<#>" + QString());
+                        if (superIsVirtual == isVirtual)
+                            f->setDeclaringClass(sf->declaringClass());
                     }
 
-
                     // Otherwise we have function shadowing and we can
                     // skip the thing...
                 } else if (cmp & AbstractMetaFunction::EqualName && !sf->isSignal()) {
@@ -1580,6 +1644,8 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass)
         for (const auto &f : std::as_const(funcsToAdd)) {
             AbstractMetaFunction *copy = f->copy();
             (*copy) += AbstractMetaFunction::AddedMethod;
+            if (f->isVirtual())
+                copy->setOverriddden(f);
             funcs.append(AbstractMetaFunctionCPtr(copy));
         }
     }
@@ -1614,12 +1680,56 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass)
         addExtraIncludesForFunction(klass, func);
     }
 
+    d->setFunctions(funcs, klass);
+
+    if (!klass->isNamespace())
+        fixSpecialFunctions(klass, avoidProtectedHack);
+
     if (hasPrivateConstructors && !hasPublicConstructors) {
         (*klass) += AbstractMetaClass::Abstract;
         (*klass) -= AbstractMetaClass::FinalInTargetLang;
     }
+}
 
-    d->setFunctions(funcs, klass);
+void AbstractMetaClass::fixSpecialFunctions(const AbstractMetaClassPtr &klass,
+                                            bool avoidProtectedHack)
+{
+    auto *d = klass->d.data();
+    auto typeEntry = klass->typeEntry();
+    // Add implicit default constructor/copy constructor since they
+    // are needed by the generators in the function overload lists.
+    auto ct = AbstractMetaClass::queryFirstFunction(klass->functions(),
+                                                    FunctionQueryOption::DefaultConstructor);
+    if (ct && ct->isPublic()) {
+        d->m_isDefaultConstructible = 1;
+    } else if (klass->canAddDefaultConstructor()) {
+        d->m_isDefaultConstructible = 1;
+        AbstractMetaClass::addDefaultConstructor(klass);
+    }
+
+    // Legacy: For the effective default constructibility of values, we apply a
+    // relaxed criterion: If a visible constructor is there, we assume
+    // minimalConstructorExpression() can guess default parameter values.
+    // FIXME PYSIDE 7: Remove protected handling?
+    bool typeSystemDefaultConstructible = d->m_isDefaultConstructible;
+    if (!typeSystemDefaultConstructible && typeEntry->isValue()) {
+        const auto flags = FunctionQueryOption::Constructors | FunctionQueryOption::Visible;
+        if (auto ct = AbstractMetaClass::queryFirstFunction(d->m_functions, flags)) {
+            typeSystemDefaultConstructible =
+                ct->isPublic() || (ct->isProtected() && !avoidProtectedHack);
+        }
+    }
+    typeEntry->setDefaultConstructibleDetected(typeSystemDefaultConstructible);
+
+    ct = AbstractMetaClass::queryFirstFunction(klass->functions(),
+                                               FunctionQueryOption::CopyConstructor);
+    if (ct && ct->isPublic()) {
+        d->m_isCopyConstructible = 1;
+    } else if (klass->canAddDefaultCopyConstructor()) {
+        d->m_isCopyConstructible = 1;
+        AbstractMetaClass::addDefaultCopyConstructor(klass);
+    }
+    typeEntry->setCopyableDetected(d->m_isCopyConstructible);
 }
 
 bool AbstractMetaClass::needsInheritanceSetup() const
@@ -1787,54 +1897,6 @@ bool AbstractMetaClass::isObjectType() const
     return d->m_typeEntry->isObject();
 }
 
-bool AbstractMetaClass::isCopyable() const
-{
-    if (isNamespace() || d->m_typeEntry->isObject())
-        return false;
-    auto copyable = d->m_typeEntry->copyable();
-    return copyable == ComplexTypeEntry::CopyableSet
-        || (copyable == ComplexTypeEntry::Unknown && isCopyConstructible());
-}
-
-bool AbstractMetaClass::isValueTypeWithCopyConstructorOnly() const
-{
-    return d->m_valueTypeWithCopyConstructorOnly;
-}
-
-void AbstractMetaClass::setValueTypeWithCopyConstructorOnly(bool v)
-{
-    d->m_valueTypeWithCopyConstructorOnly = v;
-}
-
-bool AbstractMetaClass::determineValueTypeWithCopyConstructorOnly(const AbstractMetaClassCPtr &c,
-                                                                  bool avoidProtectedHack)
-{
-
-    if (!c->typeEntry()->isValue())
-        return false;
-    if (c->attributes().testFlag(AbstractMetaClass::HasRejectedDefaultConstructor))
-        return false;
-    const auto ctors = c->queryFunctions(FunctionQueryOption::AnyConstructor);
-    bool copyConstructorFound = false;
-    for (const auto &ctor : ctors) {
-        switch (ctor->functionType()) {
-        case AbstractMetaFunction::ConstructorFunction:
-            if (!ctor->isPrivate() && (ctor->isPublic() || !avoidProtectedHack))
-                return false;
-            break;
-        case AbstractMetaFunction::CopyConstructorFunction:
-            copyConstructorFound = true;
-            break;
-        case AbstractMetaFunction::MoveConstructorFunction:
-            break;
-        default:
-            Q_ASSERT(false);
-            break;
-        }
-    }
-    return copyConstructorFound;
-}
-
 #ifndef QT_NO_DEBUG_STREAM
 
 void AbstractMetaClass::format(QDebug &debug) const
@@ -1855,6 +1917,10 @@ void AbstractMetaClass::format(QDebug &debug) const
     if (attributes().testFlag(AbstractMetaClass::Deprecated))
         debug << " [deprecated]";
 
+    if (d->m_isDefaultConstructible)
+        debug << " [default constructible]";
+    if (d->m_isCopyConstructible)
+        debug << " [copy constructible]";
     if (d->m_hasPrivateConstructor)
         debug << " [private constructor]";
     if (d->m_hasDeletedDefaultConstructor)
@@ -1867,9 +1933,12 @@ void AbstractMetaClass::format(QDebug &debug) const
         debug << " [protected destructor]";
     if (d->m_hasVirtualDestructor)
         debug << " [virtual destructor]";
-    if (d->m_valueTypeWithCopyConstructorOnly)
-        debug << " [value type with copy constructor only]";
-
+    if (d->m_hasDeletedMoveConstructor)
+        debug << " [deleted move constructor]";
+    if (d->m_hasDeletedAssigmentOperator)
+        debug << " [deleted assignment]";
+    if (d->m_hasDeletedMoveAssigmentOperator)
+        debug << " [deleted move assignment]";
     if (!d->m_baseClasses.isEmpty()) {
         debug << ", inherits ";
         for (const auto &b : d->m_baseClasses)
index 35946bb049224e8fdaf5d20f7b061f4c6157be0f..4816299b6382070f4ddd0411b25d177b4d52c472 100644 (file)
@@ -10,8 +10,8 @@
 #include "typesystem_typedefs.h"
 
 #include <QtCore/qobjectdefs.h>
-#include <QtCore/QScopedPointer>
-#include <QtCore/QStringList>
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qstringlist.h>
 
 QT_FORWARD_DECLARE_CLASS(QDebug)
 
@@ -82,6 +82,13 @@ public:
     AbstractMetaFunctionCPtr copyConstructor() const;
     bool hasCopyConstructor() const;
     bool hasPrivateCopyConstructor() const;
+    AbstractMetaFunctionCPtr moveConstructor() const;
+    bool hasMoveConstructor() const;
+    bool hasPrivateMoveConstructor() const;
+    AbstractMetaFunctionCPtr assignmentOperator() const;
+    bool hasAssignmentOperator() const;
+    AbstractMetaFunctionCPtr moveAssignmentOperator() const;
+    bool hasMoveAssignmentOperator() const;
 
     static void addDefaultConstructor(const AbstractMetaClassPtr &klass);
     static void addDefaultCopyConstructor(const AbstractMetaClassPtr &klass);
@@ -98,6 +105,15 @@ public:
     bool hasDeletedCopyConstructor() const;
     void setHasDeletedCopyConstructor(bool value);
 
+    bool hasDeletedMoveConstructor() const;
+    void setHasDeletedMoveConstructor(bool value);
+
+    bool hasDeletedAssignmentOperator() const;
+    void setHasDeletedAssignmentOperator(bool value);
+
+    bool hasDeletedMoveAssignmentOperator() const;
+    void setHasDeletedMoveAssignmentOperator(bool value);
+
     bool hasPrivateDestructor() const;
     void setHasPrivateDestructor(bool value);
 
@@ -107,13 +123,9 @@ public:
     bool hasVirtualDestructor() const;
     void setHasVirtualDestructor(bool value);
 
+    // Values detected by the code model (might be overridden by type system).
     bool isDefaultConstructible() const;
-    bool isImplicitlyDefaultConstructible() const;
-    bool canAddDefaultConstructor() const;
-
     bool isCopyConstructible() const;
-    bool isImplicitlyCopyConstructible() const;
-    bool canAddDefaultCopyConstructor() const;
 
     static void addSynthesizedComparisonOperators(const AbstractMetaClassPtr &c);
 
@@ -309,11 +321,6 @@ public:
 
     // Query functions for generators
     bool isObjectType() const;
-    bool isCopyable() const;
-    bool isValueTypeWithCopyConstructorOnly() const;
-    void setValueTypeWithCopyConstructorOnly(bool v);
-    static bool determineValueTypeWithCopyConstructorOnly(const AbstractMetaClassCPtr &c,
-                                                          bool avoidProtectedHack);
 
     static AbstractMetaClassPtr findClass(const AbstractMetaClassList &classes,
                                           QAnyStringView name);
@@ -332,7 +339,7 @@ public:
     void setSourceLocation(const SourceLocation &sourceLocation);
 
     // For AbstractMetaBuilder
-    static void fixFunctions(const AbstractMetaClassPtr &klass);
+    static void fixFunctions(const AbstractMetaClassPtr &klass, bool avoidProtectedHack);
     bool needsInheritanceSetup() const;
     void setInheritanceDone(bool b);
     bool inheritanceDone() const;
@@ -341,6 +348,10 @@ public:
     void invisibleNamespaceRecursion(Function f) const;
 
 private:
+    bool canAddDefaultConstructor() const;
+    bool canAddDefaultCopyConstructor() const;
+    static void fixSpecialFunctions(const AbstractMetaClassPtr &klass, bool avoidProtectedHack);
+
 #ifndef QT_NO_DEBUG_STREAM
     void format(QDebug &d) const;
     void formatMembers(QDebug &d) const;
index 9047c6bcd7d0508ca62517291ab896b9784aa77e..d8606ecc2bd3d78ce385e86fbe21cff5218003e4 100644 (file)
@@ -4,18 +4,21 @@
 #ifndef ABSTRACTMETALANG_ENUMS_H
 #define ABSTRACTMETALANG_ENUMS_H
 
-#include <QtCore/QFlags>
+#include <QtCore/qflags.h>
 
 enum class FunctionQueryOption {
     AnyConstructor               = 0x0000001, // Any constructor (copy/move)
     Constructors                 = 0x0000002, // Constructors except copy/move
-    CopyConstructor              = 0x0000004, // Only copy constructors
-    //Destructors                  = 0x0000002, // Only destructors. Not included in class.
-    ClassImplements              = 0x0000020, // Only functions implemented by the current class
-    StaticFunctions              = 0x0000080, // Only static functions
-    Signals                      = 0x0000100, // Only signals
-    NormalFunctions              = 0x0000200, // Only functions that aren't signals
-    Visible                      = 0x0000400, // Only public and protected functions
+    DefaultConstructor           = 0x0000004, // Only Default constructors
+    CopyConstructor              = 0x0000008, // Only copy constructors
+    MoveConstructor              = 0x0000010, // Only move constructors
+    AssignmentOperator           = 0x0000020, // Only assignment operator
+    MoveAssignmentOperator       = 0x0000040, // Only move assignment operator
+    ClassImplements              = 0x0000080, // Only functions implemented by the current class
+    StaticFunctions              = 0x0000100, // Only static functions
+    Signals                      = 0x0000200, // Only signals
+    NormalFunctions              = 0x0000400, // Only functions that aren't signals
+    Visible                      = 0x0000800, // Only public and protected functions
     NonStaticFunctions           = 0x0004000, // No static functions
     Empty                        = 0x0008000, // Empty overrides of abstract functions
     Invisible                    = 0x0010000, // Only private functions
index 27321ca2d6f4eb8366063ce0c0a9f98fe467250b..2280e0d2c1c6dd0e77b88047150333f5460c765b 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef ABSTRACTMETALANG_TYPEDEFS_H
 #define ABSTRACTMETALANG_TYPEDEFS_H
 
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
 
 #include <memory>
 
index 145b040ec1a106b3f6ec38e3ed3e6ca0e75d77ae..ac56bcc58600d43ccc2ae112c8ee9c32ce7b11f4 100644 (file)
@@ -9,17 +9,19 @@
 #include "containertypeentry.h"
 #include "enumtypeentry.h"
 #include "flagstypeentry.h"
+#include "cpptypeentry.h"
+#include "primitivetypeentry.h"
 
 #include "qtcompat.h"
 #include "typeinfo.h"
 
 #ifndef QT_NO_DEBUG_STREAM
-#  include <QtCore/QDebug>
+#  include <QtCore/qdebug.h>
 #endif
 
-#include <QtCore/QHash>
-#include <QtCore/QSharedData>
-#include <QtCore/QStack>
+#include <QtCore/qhash.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qstack.h>
 
 #include <memory>
 
@@ -111,6 +113,7 @@ public:
     AbstractMetaTypeData(const TypeEntryCPtr &t);
 
     int actualIndirections() const;
+    bool passByRef() const;
     bool passByConstRef() const;
     bool passByValue() const;
     AbstractMetaType::TypeUsagePattern determineUsagePattern() const;
@@ -194,6 +197,12 @@ QString AbstractMetaType::name() const
     return d->m_typeEntry->targetLangEntryName();
 }
 
+QString AbstractMetaType::basicPrimitiveName() const
+{
+    return d->m_typeEntry->isPrimitive()
+        ? basicReferencedTypeEntry(d->m_typeEntry)->name() : name();
+}
+
 QString AbstractMetaType::fullName() const
 {
     return d->m_typeEntry->qualifiedTargetLangName();
@@ -294,7 +303,7 @@ QString AbstractMetaType::originalTypeDescription() const
 void AbstractMetaType::setOriginalTemplateType(const AbstractMetaType &type)
 {
     if (!d->m_originalTemplateType || *d->m_originalTemplateType != type)
-        d->m_originalTemplateType.reset(new AbstractMetaType(type));
+        d->m_originalTemplateType = std::make_shared<AbstractMetaType>(type);
 }
 
 const AbstractMetaType *AbstractMetaType::originalTemplateType() const
@@ -336,9 +345,19 @@ AbstractMetaTypeList AbstractMetaType::nestedArrayTypes() const
     return result;
 }
 
+bool AbstractMetaTypeData::passByRef() const
+{
+    return m_referenceType == LValueReference && m_indirections.isEmpty();
+}
+
 bool AbstractMetaTypeData::passByConstRef() const
 {
-    return m_constant && m_referenceType == LValueReference && m_indirections.isEmpty();
+    return m_constant && passByRef();
+}
+
+bool AbstractMetaType::passByRef() const
+{
+    return d->passByRef();
 }
 
 bool AbstractMetaType::passByConstRef() const
@@ -358,7 +377,7 @@ bool AbstractMetaType::passByValue() const
 
 bool AbstractMetaType::useStdMove() const
 {
-    return (isUniquePointer() && d->passByValue())
+    return (isMoveOnlyType() && d->passByValue())
         || d->m_referenceType == RValueReference;
 }
 
@@ -446,7 +465,7 @@ const AbstractMetaType *AbstractMetaType::arrayElementType() const
 void AbstractMetaType::setArrayElementType(const AbstractMetaType &t)
 {
     if (!d->m_arrayElementType || *d->m_arrayElementType != t) {
-        d->m_arrayElementType.reset(new AbstractMetaType(t));
+        d->m_arrayElementType = std::make_shared<AbstractMetaType>(t);
         d->m_signaturesDirty = true;
     }
 }
@@ -679,7 +698,7 @@ QString AbstractMetaTypeData::formatPythonSignature() const
     if (m_pattern == AbstractMetaType::ArrayPattern) {
         // Build nested array dimensions a[2][3] in correct order
         result += m_arrayElementType->formatPythonSignature();
-        const int arrayPos = result.indexOf(u'[');
+        const auto arrayPos = result.indexOf(u'[');
         if (arrayPos != -1)
             result.insert(arrayPos, formatArraySize(m_arrayElementCount));
         else
@@ -745,6 +764,24 @@ void AbstractMetaType::setVolatile(bool v)
     }
 }
 
+bool AbstractMetaType::isDefaultConstructible() const
+{
+    auto cppTe = std::dynamic_pointer_cast<const CppTypeEntry>(d->m_typeEntry);
+    return cppTe != nullptr && cppTe->isDefaultConstructible();
+}
+
+bool AbstractMetaType::isCopyable() const
+{
+    auto cppTe = std::dynamic_pointer_cast<const CppTypeEntry>(d->m_typeEntry);
+    return cppTe != nullptr && cppTe->isCopyable();
+}
+
+bool AbstractMetaType::isMovable() const
+{
+    auto cppTe = std::dynamic_pointer_cast<const CppTypeEntry>(d->m_typeEntry);
+    return cppTe != nullptr && cppTe->isMovable();
+}
+
 static bool equalsCPtr(const AbstractMetaTypeCPtr &t1, const AbstractMetaTypeCPtr &t2)
 {
     if (bool(t1) != bool(t2))
@@ -779,12 +816,12 @@ bool AbstractMetaTypeData::equals(const AbstractMetaTypeData &rhs) const
 
 bool comparesEqual(const AbstractMetaType &lhs, const AbstractMetaType &rhs) noexcept
 {
-    return lhs.d->equals(*rhs.d);
+    return lhs.d.constData() == rhs.d.constData() || lhs.d->equals(*rhs.d);
 }
 
 bool AbstractMetaType::isEquivalent(const AbstractMetaType &rhs) const
 {
-    return  d->isEquivalent(*rhs.d);
+    return d.constData() == rhs.d.constData() || d->isEquivalent(*rhs.d);
 }
 
 const AbstractMetaType *AbstractMetaType::viewOn() const
@@ -795,7 +832,7 @@ const AbstractMetaType *AbstractMetaType::viewOn() const
 void AbstractMetaType::setViewOn(const AbstractMetaType &v)
 {
     if (!d->m_viewOn || *d->m_viewOn != v)
-        d->m_viewOn.reset(new AbstractMetaType(v));
+        d->m_viewOn = std::make_shared<AbstractMetaType>(v);
 }
 
 AbstractMetaType AbstractMetaType::createVoid()
@@ -854,11 +891,6 @@ bool AbstractMetaType::isObjectType() const
     return d->m_typeEntry->isObject();
 }
 
-bool AbstractMetaType::isUniquePointer() const
-{
-    return isSmartPointer() && d->m_typeEntry->isUniquePointer();
-}
-
 bool AbstractMetaType::isPointer() const
 {
     return !d->m_indirections.isEmpty()
@@ -929,16 +961,16 @@ bool AbstractMetaType::isExtendedCppPrimitive() const
 bool AbstractMetaType::isValueTypeWithCopyConstructorOnly() const
 {
     bool result = false;
-    if (d->m_typeEntry->isComplex()) {
-        const auto cte = std::static_pointer_cast<const ComplexTypeEntry>(d->m_typeEntry);
-        result = cte->isValueTypeWithCopyConstructorOnly();
+    if (isValue()) {
+        auto cppTe = std::static_pointer_cast<const CppTypeEntry>(d->m_typeEntry);
+        result = !cppTe->isDefaultConstructible() && cppTe->isCopyable();
     }
     return result;
 }
 
 bool AbstractMetaType::valueTypeWithCopyConstructorOnlyPassed() const
 {
-    return (passByValue() || passByConstRef())
+    return (passByValue() || passByRef())
            && isValueTypeWithCopyConstructorOnly();
 }
 
index 8a1ecdf205993642973ae89e301c08c89128cc16..9dd2f09be714646d5d893f7aa01aa33d5be4956b 100644 (file)
@@ -9,12 +9,12 @@
 #include "parser/codemodel_enums.h"
 #include "typedatabase_typedefs.h"
 
-#include <QtCore/QtCompare>
+#include <QtCore/qcompare.h>
 #include <QtCore/qobjectdefs.h>
-#include <QtCore/QHashFunctions>
-#include <QtCore/QSharedDataPointer>
-#include <QtCore/QList>
-#include <QtCore/QSet>
+#include <QtCore/qhashfunctions.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qset.h>
 
 #include <optional>
 
@@ -58,6 +58,8 @@ public:
 
     QString package() const;
     QString name() const;
+    /// For a C++ primitive type, resolve the name ("quint32"->"unsigned int")
+    QString basicPrimitiveName() const;
     QString fullName() const;
 
     void setTypeUsagePattern(TypeUsagePattern pattern);
@@ -106,7 +108,6 @@ public:
 
     // returns true if the type was used as a smart pointer
     bool isSmartPointer() const { return typeUsagePattern() == SmartPointerPattern; }
-    bool isUniquePointer() const;
 
     // returns true if the type was used as a flag
     bool isFlags() const { return typeUsagePattern() == FlagsPattern; }
@@ -119,6 +120,14 @@ public:
     bool isVolatile() const;
     void setVolatile(bool v);
 
+    // Typesystem specification, potentially overriding the code model detection
+    bool isDefaultConstructible() const;
+    bool isCopyable() const;
+    bool isMovable() const;
+
+    bool isMoveOnlyType() const { return !isCopyable() && isMovable(); }
+
+    bool passByRef() const;
     bool passByConstRef() const;
     bool passByValue() const;
     bool useStdMove() const;
index ac2ca5f344eb6b7f8d268d013c91bd87f0a02759..ee8009cfee6a1964718c1823e30d6e3c677bd716 100644 (file)
@@ -5,7 +5,7 @@
 #include "addedfunction_p.h"
 #include "typeparser.h"
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 using namespace Qt::StringLiterals;
 
@@ -54,6 +54,8 @@ static qsizetype parameterTokenEnd(qsizetype startPos, QStringView paramString)
         case ']':
             --nestingLevel;
             break;
+        default:
+            break;
         }
     }
     return end;
index b8d189b7a62e3f6248b8ce991fdee1f440b2513f..6eadc337bb8c9b6232cd48a9284a14b796b0b344 100644 (file)
@@ -7,8 +7,8 @@
 #include "modifications.h"
 #include "parser/typeinfo.h"
 
-#include <QtCore/QList>
-#include <QtCore/QString>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
 
 #include <memory>
 
index 40b69a5dfa857ac88548d2a8befc2193502b9fc9..c3701c0a6e58d87ca7d9b3a59f04cf3ecd42390a 100644 (file)
@@ -4,10 +4,10 @@
 #ifndef ADDEDFUNCTION_P_H
 #define ADDEDFUNCTION_P_H
 
-#include <QtCore/QtCompare>
-#include <QtCore/QList>
-#include <QtCore/QString>
-#include <QtCore/QStringView>
+#include <QtCore/qcompare.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringview.h>
 
 QT_BEGIN_NAMESPACE
 class QDebug;
index c7a2245337575c580a240321abf37a57eb0e6c7d..fcff162053cfd0894316117f9c039bde0f56e3b9 100644 (file)
@@ -4,9 +4,9 @@
 #include "anystringview_helpers.h"
 
 #include <QtCore/QString> // Must go before QAnyStringView for operator<<(QTextStream,QASV)!
-#include <QtCore/QAnyStringView>
-#include <QtCore/QDebug>
-#include <QtCore/QTextStream>
+#include <QtCore/qanystringview.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qtextstream.h>
 
 #include <cstring>
 
index f1c49e041ef2cd7d1704139980b21eaf609f9ad4..9943c7dbbb0afed3de58016b53c6b04762566519 100644 (file)
@@ -4,8 +4,8 @@
 #ifndef ANYSTRINGVIEW_STREAM_H
 #define ANYSTRINGVIEW_STREAM_H
 
-#include <QtCore/QtTypes>
-#include <QtCore/QtClassHelperMacros>
+#include <QtCore/qtypes.h>
+#include <QtCore/qtclasshelpermacros.h>
 
 QT_FORWARD_DECLARE_CLASS(QAnyStringView)
 QT_FORWARD_DECLARE_CLASS(QTextStream)
index f3d9d0b5899118cca87d427e7a79ba20529bc232..38dedfd148fd078214cc4c7f7a7b1aa29532c03b 100644 (file)
@@ -27,9 +27,9 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QDir>
-#include <QtCore/QDebug>
-#include <QtCore/QTemporaryFile>
+#include <QtCore/qdir.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qtemporaryfile.h>
 
 #include <algorithm>
 #include <iostream>
@@ -322,12 +322,12 @@ static void addPySideExtensions(QByteArrayList *a)
     // sequence of properties will to expand to a sequence of annotations
     // annotating nothing, causing clang to complain. Instead, define it away in a
     // static assert with the stringified argument in a ','-operator (cf qdoc).
-    a->append(QByteArrayLiteral("-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);"));
+    a->append("-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);"_ba);
 
     // With Qt6, qsimd.h became public header and was included in <QtCore>. That
     // introduced a conflict with libclang headers on macOS. To be able to include
     // <QtCore>, we prevent its inclusion by adding its include guard.
-    a->append(QByteArrayLiteral("-DQSIMD_H"));
+    a->append("-DQSIMD_H"_ba);
 }
 
 bool ApiExtractorPrivate::runHelper(ApiExtractorFlags flags)
@@ -370,6 +370,7 @@ bool ApiExtractorPrivate::runHelper(ApiExtractorFlags flags)
 
     bool addCompilerSupportArguments = true;
     if (clangOptionsSize > 0) {
+        clang::setTargetTriple(m_clangOptions);
         qsizetype i = 0;
         if (m_clangOptions.at(i) == u"-") {
             ++i;
@@ -450,6 +451,24 @@ static inline void classListToCList(const AbstractMetaClassList &list, AbstractM
     std::copy(list.cbegin(), list.cend(), std::back_inserter(*target));
 }
 
+static QString nameFromType(const InstantiatedSmartPointer &ismp)
+{
+    return ismp.type.cppSignature();
+}
+
+static void logInstantiations(QString title, QStringList nameList)
+{
+    if (!nameList.isEmpty()) {
+        std::sort(nameList.begin(), nameList.end());
+        title += " ("_L1 + QString::number(nameList.size()) + u')';
+        const auto underLine = QString(title.size(), u'-');
+        ReportHandler::addGeneralMessage(u'\n' + title + u'\n' + underLine + u'\n');
+        for (const auto &e : nameList)
+            ReportHandler::addGeneralMessage(" * "_L1 + e);
+        ReportHandler::addGeneralMessage(QString(1, u'\n'));
+    }
+}
+
 std::optional<ApiExtractorResult> ApiExtractor::run(ApiExtractorFlags flags)
 {
     if (!d->runHelper(flags))
@@ -475,6 +494,15 @@ std::optional<ApiExtractorResult> ApiExtractor::run(ApiExtractorFlags flags)
     }
     result.m_instantiatedSmartPointers =
         topologicalSortSmartPointers(collectContext.instantiatedSmartPointers);
+
+    logInstantiations("Instantiated Containers"_L1, collectContext.instantiatedContainersNames);
+    QStringList smartPointers;
+    std::transform(collectContext.instantiatedSmartPointers.cbegin(),
+                   collectContext.instantiatedSmartPointers.cend(),
+                   std::back_inserter(smartPointers), nameFromType);
+    logInstantiations("Instantiated Smart Pointers"_L1, smartPointers);
+
+
     return result;
 }
 
index a15005e4436a4c512650325d153b021a0143968d..c7a9ef2179660cca3197ec2ceb8c94573fd7344f 100644 (file)
@@ -10,8 +10,8 @@
 #include "clangparser/compilersupport.h"
 #include "typesystem_typedefs.h"
 
-#include <QtCore/QFileInfoList>
-#include <QtCore/QStringList>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qstringlist.h>
 
 #include <optional>
 
index 6f69b8b77f1d52484f956f653bdf752372058fd8..5fb9fd337787efc3ef00facb66962737e9d99b27 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef APIEXTRACTORFLAGS_H
 #define APIEXTRACTORFLAGS_H
 
-#include <QtCore/QFlags>
+#include <QtCore/qflags.h>
 
 enum class ApiExtractorFlag
 {
index d862286a066fa1f36fb22375165141235668e8ae..b72b51ae104ed2e41b857a815e60842aab1b8982 100644 (file)
@@ -9,8 +9,7 @@
 #include "abstractmetalang_typedefs.h"
 #include "typesystem_typedefs.h"
 
-#include <QtCore/QHash>
-#include <QtCore/QMultiHash>
+#include <QtCore/qhash.h>
 
 #include <optional>
 
index 5b9bb191ece0f460b71c8100456a449b74356a69..e3eafe38e22c6526e80330cf654103049afcf3e1 100644 (file)
@@ -4,11 +4,11 @@
 #ifndef ARRAYTYPEENTRY_H
 #define ARRAYTYPEENTRY_H
 
-#include "typesystem.h"
+#include "cpptypeentry.h"
 
 class ArrayTypeEntryPrivate;
 
-class ArrayTypeEntry : public TypeEntry
+class ArrayTypeEntry : public CppTypeEntry
 {
 public:
     explicit ArrayTypeEntry(const TypeEntryCPtr &nested_type, const QVersionNumber &vr,
index 06216c2eaaf6629caf6f7cfc65bd8821baec8264..5188262de68a921a7a89455d734f3d846343df2f 100644 (file)
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QHash>
-#include <QtCore/QMap>
-#include <QtCore/QString>
-#include <QtCore/QStack>
-#include <QtCore/QList>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qlist.h>
 
 using namespace Qt::StringLiterals;
 
@@ -190,7 +190,7 @@ public:
     bool visitHeader(const QString &fileName) const;
     static const char *specialSystemHeaderReason(SpecialSystemHeader sh);
 
-    void setFileName(const CXCursor &cursor, _CodeModelItem *item);
+    void setFileName(const CXCursor &cursor, _CodeModelItem *item) const;
 
     BaseVisitor *m_baseVisitor;
     CodeModel *m_model;
@@ -225,7 +225,7 @@ public:
 bool BuilderPrivate::addClass(const CXCursor &cursor, CodeModel::ClassType t)
 {
     QString className = getCursorSpelling(cursor);
-    m_currentClass.reset(new _ClassModelItem(m_model, className));
+    m_currentClass = std::make_shared<_ClassModelItem>(m_model, className);
     setFileName(cursor, m_currentClass.get());
     m_currentClass->setClassType(t);
     // Some inner class? Note that it does not need to be (lexically) contained in a
@@ -369,6 +369,14 @@ static inline CodeModel::FunctionType functionTypeFromCursor(const CXCursor &cur
     case CXCursor_Destructor:
         result = CodeModel::Destructor;
         break;
+    case CXCursor_CXXMethod:
+#ifdef CLANG_HAS_ASSIGNMENT_OPERATOR_CHECK
+        if (clang_CXXMethod_isCopyAssignmentOperator(cursor) != 0)
+            result = CodeModel::AssignmentOperator;
+        else if (clang_CXXMethod_isMoveAssignmentOperator(cursor) != 0)
+            result = CodeModel::MoveAssignmentOperator;
+#endif
+        break;
     default:
         break;
     }
@@ -631,7 +639,7 @@ ClassModelItem BuilderPrivate::currentTemplateClass() const
 void BuilderPrivate::startTemplateTypeAlias(const CXCursor &cursor)
 {
     const QString target = getCursorSpelling(cursor);
-    m_currentTemplateTypeAlias.reset(new _TemplateTypeAliasModelItem(m_model, target));
+    m_currentTemplateTypeAlias = std::make_shared<_TemplateTypeAliasModelItem>(m_model, target);
     setFileName(cursor, m_currentTemplateTypeAlias.get());
     m_currentTemplateTypeAlias->setScope(m_scope);
 }
@@ -741,7 +749,7 @@ void BuilderPrivate::addBaseClass(const CXCursor &cursor)
     m_currentClass->addBaseClass({baseClass.first, baseClass.second, access});
 }
 
-void BuilderPrivate::setFileName(const CXCursor &cursor, _CodeModelItem *item)
+void BuilderPrivate::setFileName(const CXCursor &cursor, _CodeModelItem *item) const
 {
     const SourceRange range = getCursorRange(cursor);
     QString file = m_baseVisitor->getFileName(range.first.file);
@@ -929,6 +937,12 @@ static QString enumType(const CXCursor &cursor)
 
 BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor)
 {
+    // Skip inline code
+    if ((cursor.kind >= CXCursor_FirstExpr && cursor.kind <= CXCursor_LastExpr)
+        || (cursor.kind >= CXCursor_FirstStmt && cursor.kind <= CXCursor_LastStmt)) {
+        return Skip;
+    }
+
     switch (cursor.kind) {
     case CXCursor_CXXAccessSpecifier:
         d->m_currentFunctionType = CodeModel::Normal;
@@ -982,7 +996,7 @@ BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor)
 #endif
             kind = EnumClass;
         }
-        d->m_currentEnum.reset(new _EnumModelItem(d->m_model, name));
+        d->m_currentEnum = std::make_shared<_EnumModelItem>(d->m_model, name);
         d->setFileName(cursor, d->m_currentEnum.get());
         d->m_currentEnum->setScope(d->m_scope);
         d->m_currentEnum->setEnumKind(kind);
@@ -1009,7 +1023,7 @@ BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor)
         else
             enumValue.setUnsignedValue(clang_getEnumConstantDeclUnsignedValue(cursor));
         auto enumConstant = std::make_shared<_EnumeratorModelItem>(d->m_model, name);
-        enumConstant->setStringValue(d->cursorValueExpression(this, cursor));
+        enumConstant->setStringValue(BuilderPrivate::cursorValueExpression(this, cursor));
         enumConstant->setValue(enumValue);
         if (clang_getCursorAvailability(cursor) == CXAvailability_Deprecated)
             enumConstant->setDeprecated(true);
@@ -1029,8 +1043,6 @@ BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor)
     case CXCursor_FriendDecl:
         d->m_withinFriendDecl = true;
         break;
-    case CXCursor_CompoundStmt: // Function bodies
-        return Skip;
     case CXCursor_Constructor:
     case CXCursor_Destructor: // Note: Also use clang_CXXConstructor_is..Constructor?
     case CXCursor_CXXMethod:
@@ -1092,7 +1104,7 @@ BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor)
         // Treat namespaces separately to allow for extending namespaces
         // in subsequent modules.
         NamespaceModelItem namespaceItem = parentNamespaceItem->findNamespace(name);
-        namespaceItem.reset(new _NamespaceModelItem(d->m_model, name));
+        namespaceItem = std::make_shared<_NamespaceModelItem>(d->m_model, name);
         d->setFileName(cursor, namespaceItem.get());
         namespaceItem->setScope(d->m_scope);
         namespaceItem->setType(type);
@@ -1105,12 +1117,12 @@ BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor)
         // and function pointer typedefs.
         if (!d->m_currentArgument && d->m_currentFunction) {
             const QString name = getCursorSpelling(cursor);
-            d->m_currentArgument.reset(new _ArgumentModelItem(d->m_model, name));
+            d->m_currentArgument = std::make_shared<_ArgumentModelItem>(d->m_model, name);
             const auto type = clang_getCursorType(cursor);
             d->m_currentArgument->setScopeResolution(hasScopeResolution(type));
             d->m_currentArgument->setType(d->createTypeInfo(type));
             d->m_currentFunction->addArgument(d->m_currentArgument);
-            QString defaultValueExpression = d->cursorValueExpression(this, cursor);
+            QString defaultValueExpression = BuilderPrivate::cursorValueExpression(this, cursor);
             if (!defaultValueExpression.isEmpty()) {
                 d->m_currentArgument->setDefaultValueExpression(defaultValueExpression);
                 d->m_currentArgument->setDefaultValue(true);
index 218aa6163a869eb2b56396a43337bdc3cd52f05a..f60bbe155d29279049a33535724038aceb013089 100644 (file)
@@ -8,6 +8,11 @@
 
 #include <codemodel_fwd.h>
 
+
+#if CINDEX_VERSION_MAJOR > 0 || CINDEX_VERSION_MINOR >= 63 // Clang 16
+#  define CLANG_HAS_ASSIGNMENT_OPERATOR_CHECK
+#endif
+
 namespace clang {
 
 class BuilderPrivate;
index 7c69963733534ef1424c64d55c821b8cfc079182..38fdf0bbd0b808dcf358cfddaa5ec5b3f3643ae8 100644 (file)
@@ -4,8 +4,8 @@
 #include "clangdebugutils.h"
 #include "clangutils.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QString>
+#include <QtCore/qdebug.h>
+#include <QtCore/qstring.h>
 
 #ifndef QT_NO_DEBUG_STREAM
 
index 43334c30648f455609d021fab5ba245e58b87707..747937ede8b9a6e897420d18f12972d0f9614a61 100644 (file)
@@ -5,13 +5,14 @@
 #include "clangutils.h"
 #include "clangdebugutils.h"
 #include "compilersupport.h"
+#include "reporthandler.h"
 
-#include <QtCore/QByteArrayList>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QScopedArrayPointer>
-#include <QtCore/QString>
+#include <QtCore/qbytearraylist.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qstring.h>
 
 using namespace Qt::StringLiterals;
 
@@ -28,8 +29,6 @@ QString SourceFileCache::getFileName(CXFile file)
 std::string_view SourceFileCache::getCodeSnippet(const CXCursor &cursor,
                                                  QString *errorMessage)
 {
-    static const char empty[] = "";
-
     if (errorMessage)
         errorMessage->clear();
 
@@ -37,12 +36,12 @@ std::string_view SourceFileCache::getCodeSnippet(const CXCursor &cursor,
     // Quick check for equal locations: Frequently happens if the code is
     // the result of a macro expansion
     if (range.first == range.second)
-         return std::string_view(empty, 0);
+        return {};
 
     if (range.first.file != range.second.file) {
         if (errorMessage)
             *errorMessage = "Range spans several files"_L1;
-        return std::string_view(empty, 0);
+        return {};
     }
 
     auto it = m_fileBufferCache.find(range.first.file);
@@ -51,7 +50,7 @@ std::string_view SourceFileCache::getCodeSnippet(const CXCursor &cursor,
         if (fileName.isEmpty()) {
             if (errorMessage)
                  *errorMessage = "Range has no file"_L1;
-            return std::string_view(empty, 0);
+            return {};
         }
         QFile file(fileName);
         if (!file.open(QIODevice::ReadOnly)) {
@@ -60,7 +59,7 @@ std::string_view SourceFileCache::getCodeSnippet(const CXCursor &cursor,
                 str << "Cannot open \"" << QDir::toNativeSeparators(fileName)
                     << "\": " << file.errorString();
             }
-            return std::string_view(empty, 0);
+            return {};
         }
         it = m_fileBufferCache.insert(range.first.file, file.readAll());
     }
@@ -76,7 +75,7 @@ std::string_view SourceFileCache::getCodeSnippet(const CXCursor &cursor,
                 << QDir::toNativeSeparators(getFileName(range.first.file))
                 << "\" (" << contents.size() << ')';
         }
-        return std::string_view(empty, 0);
+        return {};
     }
 
     return std::string_view(contents.constData() + pos, end - pos);
@@ -262,7 +261,7 @@ static CXTranslationUnit createTranslationUnit(CXIndex index,
     clangArgs += detectVulkan();
     clangArgs += args;
     QScopedArrayPointer<const char *> argv(byteArrayListToFlatArgV(clangArgs));
-    qDebug().noquote().nospace() << msgCreateTranslationUnit(clangArgs, flags);
+    ReportHandler::addGeneralMessage(QString::fromUtf8(msgCreateTranslationUnit(clangArgs, flags)));
 
     CXTranslationUnit tu{};
     CXErrorCode err = clang_parseTranslationUnit2(index, nullptr, argv.data(),
@@ -276,6 +275,24 @@ static CXTranslationUnit createTranslationUnit(CXIndex index,
     return tu;
 }
 
+static void setupTarget(CXTranslationUnit translationUnit)
+{
+    const CXTargetInfo targetInfo = clang_getTranslationUnitTargetInfo(translationUnit);
+    const auto tripleCS = clang_TargetInfo_getTriple(targetInfo);
+    clang::setPointerSize(clang_TargetInfo_getPointerWidth(targetInfo));
+    clang::setTargetTriple(QString::fromUtf8(clang_getCString(tripleCS)));
+    clang_disposeString(tripleCS);
+
+    QString message;
+    {
+        QTextStream str(&message);
+        str << "CLANG v" << CINDEX_VERSION_MAJOR << '.' << CINDEX_VERSION_MINOR
+            << " targeting \"" << targetTriple() << "\", " << pointerSize() << "bit.";
+    }
+    qCInfo(lcShiboken, "%s", qPrintable(message));
+    ReportHandler::addGeneralMessage(message + u'\n');
+}
+
 /* clangFlags are flags to clang_parseTranslationUnit2() such as
  * CXTranslationUnit_KeepGoing (from CINDEX_VERSION_MAJOR/CINDEX_VERSION_MINOR 0.35)
  */
@@ -296,6 +313,8 @@ bool parse(const QByteArrayList  &clangArgs, bool addCompilerSupportArguments,
     if (!translationUnit)
         return false;
 
+    setupTarget(translationUnit);
+
     CXCursor rootCursor = clang_getTranslationUnitCursor(translationUnit);
 
     clang_visitChildren(rootCursor, visitorCallback, reinterpret_cast<CXClientData>(&bv));
index 22e0a50cd6f8d3b787731b0063c3df90eef4019f..d7667eaa33e22da721a0bafd0a8b6da07a32c1f9 100644 (file)
@@ -6,10 +6,10 @@
 
 #include <clang-c/Index.h>
 
-#include <QtCore/QByteArrayList>
-#include <QtCore/QHash>
-#include <QtCore/QString>
-#include <QtCore/QList>
+#include <QtCore/qbytearraylist.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qlist.h>
 
 #include <string_view>
 #include <utility>
index 65d02d18040b03723412cb619e23ea2dc171d3e6..384fd68159fd836b58d41ca1902948ab85951c58 100644 (file)
@@ -3,10 +3,10 @@
 
 #include "clangutils.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QHashFunctions>
-#include <QtCore/QProcess>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qhashfunctions.h>
+#include <QtCore/qprocess.h>
 
 #include <string_view>
 
index fbbf95f1bda993583d2319c6c485a42ccdf5c177..575bb9f078664e4bbc8a97beefc47102dd23d046 100644 (file)
@@ -5,10 +5,10 @@
 #define CLANGUTILS_H
 
 #include <clang-c/Index.h>
-#include <QtCore/QString>
-#include <QtCore/QStringList>
-#include <QtCore/QtCompare>
-#include <QtCore/QList>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qcompare.h>
+#include <QtCore/qlist.h>
 
 #include <functional>
 #include <utility>
index 20224020b9b29deb75d85740acb5c9db8f2d5782..4d93a084f9d2d7af43f82afdcf030f47c6ee84cc 100644 (file)
@@ -9,14 +9,14 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfo>
-#include <QtCore/QProcess>
-#include <QtCore/QStandardPaths>
-#include <QtCore/QStringList>
-#include <QtCore/QVersionNumber>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qstandardpaths.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qversionnumber.h>
 
 #include <clang-c/Index.h>
 
@@ -59,6 +59,9 @@ bool setCompiler(const QString &name)
 
 QString _compilerPath; // Pre-defined compiler path (from command line)
 
+static unsigned _pointerSize = QT_POINTER_SIZE * 8;
+static QString _targetTriple;
+
 const QString &compilerPath()
 {
     return _compilerPath;
@@ -153,14 +156,15 @@ static void filterHomebrewHeaderPaths(HeaderPaths &headerPaths)
     if (homebrewPrefix.isEmpty())
         return;
 
-    qCInfo(lcShiboken) << "Found HOMEBREW_OPT with value:" << homebrewPrefix
-                       << "Assuming homebrew build environment.";
+    ReportHandler::addGeneralMessage("Found HOMEBREW_OPT with value:"_L1
+                                     + QString::fromUtf8(homebrewPrefix)
+                                     + "\nAssuming homebrew build environment."_L1);
 
     HeaderPaths::iterator it = headerPaths.begin();
     while (it != headerPaths.end()) {
         if (it->path.startsWith(homebrewPrefix)) {
-            qCInfo(lcShiboken) << "Filtering out homebrew include path: "
-                               << it->path;
+            ReportHandler::addGeneralMessage("Filtering out homebrew include path: "_L1
+                                             + QString::fromUtf8(it->path));
             it = headerPaths.erase(it);
         } else {
             ++it;
@@ -186,12 +190,6 @@ static HeaderPaths gppInternalIncludePaths(const QString &compiler)
     const QByteArrayList stdErrLines = stdErr.split('\n');
     bool isIncludeDir = false;
 
-    if (ReportHandler::isDebug(ReportHandler::MediumDebug))
-        qCInfo(lcShiboken()).noquote().nospace()
-            << "gppInternalIncludePaths:\n    compiler: " << compiler
-            << "\n    stdOut: " << stdOut
-            << "\n    stdErr: " << stdErr;
-
     for (const QByteArray &line : stdErrLines) {
         if (isIncludeDir) {
             if (line.startsWith(QByteArrayLiteral("End of search list"))) {
@@ -204,7 +202,7 @@ static HeaderPaths gppInternalIncludePaths(const QString &compiler)
                 }
                 result.append(headerPath);
             }
-        } else if (line.startsWith(QByteArrayLiteral("#include <...> search starts here"))) {
+        } else if (line.startsWith("#include <...> search starts here"_ba)) {
             isIncludeDir = true;
         }
     }
@@ -212,6 +210,17 @@ static HeaderPaths gppInternalIncludePaths(const QString &compiler)
     if (platform() == Platform::macOS)
         filterHomebrewHeaderPaths(result);
 
+    QString message;
+    {
+        QTextStream str(&message);
+        str << "gppInternalIncludePaths:\n    compiler: " << compiler  << '\n';
+        for (const auto &h : result)
+            str << "    " << h.path << '\n';
+        if (ReportHandler::isDebug(ReportHandler::MediumDebug))
+            str << "    stdOut: " << stdOut << "\n    stdErr: " << stdErr;
+    }
+    ReportHandler::addGeneralMessage(message);
+
     return result;
 }
 
@@ -221,9 +230,7 @@ QByteArrayList detectVulkan()
     static const char *vulkanVariables[] = {"VULKAN_SDK", "VK_SDK_PATH"};
     for (const char *vulkanVariable : vulkanVariables) {
         if (qEnvironmentVariableIsSet(vulkanVariable)) {
-            const auto option = QByteArrayLiteral("-isystem")
-                                + qgetenv(vulkanVariable)
-                                + QByteArrayLiteral("/include");
+            const auto option = "-isystem"_ba + qgetenv(vulkanVariable) + "/include"_ba;
             return {option};
         }
     }
@@ -367,11 +374,10 @@ static void appendClangBuiltinIncludes(HeaderPaths *p)
                   "(neither by checking the environment variables LLVM_INSTALL_DIR, CLANG_INSTALL_DIR "
                   " nor running llvm-config). This may lead to parse errors.");
     } else {
-        qCInfo(lcShiboken, "CLANG v%d.%d, builtins includes directory: %s",
-               CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR,
-               qPrintable(clangBuiltinIncludesDir));
         p->append(HeaderPath{QFile::encodeName(clangBuiltinIncludesDir),
                              HeaderType::System});
+        ReportHandler::addGeneralMessage("CLANG builtins includes directory: "_L1
+                                         + clangBuiltinIncludesDir);
     }
 }
 
@@ -453,4 +459,33 @@ LanguageLevel languageLevelFromOption(const char *o)
     return LanguageLevel::Default;
 }
 
+unsigned pointerSize()
+{
+    return _pointerSize;
+}
+
+void setPointerSize(unsigned ps)
+{
+    _pointerSize = ps;
+}
+
+QString targetTriple()
+{
+    return _targetTriple;
+
+}
+void setTargetTriple(const QString &t)
+{
+    _targetTriple = t;
+}
+
+void setTargetTriple(const QStringList &clangOptions)
+{
+    static constexpr auto targetOption = "--target="_L1;
+    auto targetOptionPred = [](const QString &o) { return o.startsWith(targetOption); };
+    const auto it = std::find_if(clangOptions.cbegin(), clangOptions.cend(), targetOptionPred);
+    if (it != clangOptions.cend())
+        _targetTriple = it->sliced(targetOption.size());
+}
+
 } // namespace clang
index f1d63b7c36bcef873d54f3cdeeba539c22f3f76f..0e12ca13732ddbff435d8f6c31278631d3be778e 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef COMPILERSUPPORT_H
 #define COMPILERSUPPORT_H
 
-#include <QtCore/QByteArrayList>
+#include <QtCore/qbytearraylist.h>
 
 QT_FORWARD_DECLARE_CLASS(QVersionNumber)
 QT_FORWARD_DECLARE_CLASS(QString)
@@ -51,6 +51,14 @@ void setCompilerPath(const QString &name);
 
 Platform platform();
 bool setPlatform(const QString &name);
+
+unsigned pointerSize(); // (bit)
+void setPointerSize(unsigned ps); // Set by parser
+
+QString targetTriple();
+void setTargetTriple(const QStringList &clangOptions); // Set from cmd line before parsing
+void setTargetTriple(const QString &t); // Updated by clang parser while parsing
+
 } // namespace clang
 
 #endif // COMPILERSUPPORT_H
index 54f721315b4e0190a3c3612a3bd612d1f13dfb49..1e721b5899710a600b153fe526d78476c3f429e1 100644 (file)
@@ -5,12 +5,10 @@
 #include "messages.h"
 #include "debughelpers_p.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QBuffer>
-#include <QtCore/QFile>
-#include <QtCore/QXmlStreamReader>
-#include <QtCore/QXmlStreamAttributes>
-#include <QtCore/QXmlStreamWriter>
+#include <QtCore/qdebug.h>
+#include <QtCore/qbuffer.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qxmlstream.h>
 
 #include <algorithm>
 
index df834ff35ec321834e57fbd8b35e889d3a13736b..fc1c4fdd903eaf24fc721dbc7ed0532f16b375a5 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef CLASSDOCUMENTATION_H
 #define CLASSDOCUMENTATION_H
 
-#include <QtCore/QStringList>
+#include <QtCore/qstringlist.h>
 
 #include <optional>
 
index e2cd5eb35dfcadc99e643109b41ebe3707d2b6bb..26bacf08b18bba81892193014b2b766631db8eeb 100644 (file)
@@ -7,7 +7,7 @@
 #include "exception.h"
 #include "typedatabase.h"
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 using namespace Qt::StringLiterals;
 
@@ -33,32 +33,101 @@ QString TemplateInstance::expandCode() const
     return result;
 }
 
+bool comparesEqual(const TemplateInstance &lhs, const TemplateInstance &rhs) noexcept
+{
+    return lhs.m_name == rhs.m_name && lhs.replaceRules == rhs.replaceRules;
+}
+
 // ---------------------- CodeSnipFragment
-QString CodeSnipFragment::code() const
+
+static QString fragmentToCodeHelper(const QString &c)
 {
-    return m_instance ? m_instance->expandCode() : m_code;
+    return c;
 }
 
-// ---------------------- CodeSnipAbstract
-QString CodeSnipAbstract::code() const
+static QString fragmentToCodeHelper(const TemplateInstance &p)
 {
-    QString res;
-    for (const CodeSnipFragment &codeFrag : codeList)
-        res.append(codeFrag.code());
+    return p.expandCode();
+}
 
-    return res;
+static QString fragmentToCode(const CodeSnipFragment &codeFrag)
+{
+    return std::visit([](auto f) { return fragmentToCodeHelper(f); }, codeFrag);
+}
+
+static bool isEmptyFragmentHelper(const QString &c)
+{
+    return c.isEmpty();
+}
+
+static bool isEmptyFragmentHelper(const TemplateInstance &)
+{
+    return false;
+}
+
+static bool isEmptyFragment(const CodeSnipFragment &codeFrag)
+{
+    return std::visit([](auto f) { return isEmptyFragmentHelper(f); }, codeFrag);
+}
+
+static size_t hashHelper(const QString &c, size_t seed) noexcept
+{
+    return qHash(c, seed);
+}
+
+static size_t hashHelper(const TemplateInstance &t, size_t seed) noexcept
+{
+    return qHash(t, seed);
 }
 
+size_t qHash(const CodeSnipFragment &codeFrag, size_t seed) noexcept
+{
+    return std::visit([seed](auto f) { return hashHelper(f, seed); }, codeFrag);
+}
+
+static void formatDebugHelper(QDebug &d, const QString &code)
+{
+    const auto lines = QStringView{code}.split(u'\n');
+    for (qsizetype i = 0, size = lines.size(); i < size; ++i) {
+        if (i)
+            d << "\\n";
+        d << lines.at(i).trimmed();
+    }
+}
+
+static void formatDebugHelper(QDebug &d, const TemplateInstance &t)
+{
+     d << "template=\"" << t.name() << '"';
+}
+
+QDebug operator<<(QDebug d, const CodeSnipFragment &codeFrag)
+{
+    QDebugStateSaver saver(d);
+    d.noquote();
+    d.nospace();
+    std::visit([&d](auto f) { formatDebugHelper(d, f); }, codeFrag);
+    return d;
+}
+
+// ---------------------- CodeSnipAbstract
+
 void CodeSnipAbstract::addCode(const QString &code)
 {
-    codeList.append(CodeSnipFragment(fixSpaces(code)));
+    m_codeList.emplace_back(CodeSnipFragment(fixSpaces(code)));
+}
+
+QString CodeSnipAbstract::code() const
+{
+    QString res;
+    for (const auto &codeFrag : m_codeList)
+        res.append(fragmentToCode(codeFrag));
+    return res;
 }
 
 void CodeSnipAbstract::purgeEmptyFragments()
 {
-    auto end = std::remove_if(codeList.begin(), codeList.end(),
-                              [](const CodeSnipFragment &f) { return f.isEmpty(); });
-    codeList.erase(end, codeList.end());
+    auto end = std::remove_if(m_codeList.begin(), m_codeList.end(), isEmptyFragment);
+    m_codeList.erase(end, m_codeList.end());
 }
 
 QRegularExpression CodeSnipAbstract::placeHolderRegex(int index)
@@ -66,6 +135,12 @@ QRegularExpression CodeSnipAbstract::placeHolderRegex(int index)
     return QRegularExpression(u'%' + QString::number(index) + "\\b"_L1);
 }
 
+bool comparesEqual(const CodeSnip &lhs, const CodeSnip &rhs) noexcept
+{
+    return lhs.language == rhs.language && lhs.position == rhs.position
+        && lhs.codeList() == rhs.codeList();
+}
+
 void purgeEmptyCodeSnips(QList<CodeSnip> *list)
 {
     for (auto it = list->begin(); it != list->end(); ) {
index 86834a1db41d6b6b217cbaad6edb07c8cd9e0522..b3b428991106bbaaf5779018d14c853c60aeb531 100644 (file)
@@ -7,20 +7,22 @@
 #include "codesniphelpers.h"
 #include "typesystem_enums.h"
 
-#include <QtCore/QList>
-#include <QtCore/QHash>
-#include <QtCore/QString>
+#include <QtCore/qlist.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qstring.h>
 
-#include <memory>
+#include <variant>
+
+QT_FORWARD_DECLARE_CLASS(QDebug)
 
 class TemplateInstance
 {
 public:
-    explicit TemplateInstance(const QString &name) : m_name(name) {}
+    explicit TemplateInstance(QString name) : m_name(std::move(name)) {}
 
     void addReplaceRule(const QString &name, const QString &value)
     {
-        replaceRules[name] = value;
+        replaceRules.insert(name, value);
     }
 
     QString expandCode() const;
@@ -31,60 +33,53 @@ public:
     }
 
 private:
-    const QString m_name;
-    QHash<QString, QString> replaceRules;
-};
+    Q_DECLARE_EQUALITY_COMPARABLE(TemplateInstance)
 
-using TemplateInstancePtr = std::shared_ptr<TemplateInstance>;
-
-class CodeSnipFragment
-{
-public:
-    CodeSnipFragment() = default;
-    explicit CodeSnipFragment(const QString &code) : m_code(code) {}
-    explicit CodeSnipFragment(const TemplateInstancePtr &instance) : m_instance(instance) {}
+    friend bool comparesEqual(const TemplateInstance &lhs, const TemplateInstance &rhs) noexcept;
+    friend size_t qHash(const TemplateInstance &t, size_t seed = 0) noexcept
+    { return qHashMulti(seed, t.m_name, t.replaceRules); }
 
-    bool isEmpty() const { return m_code.isEmpty() && !m_instance; }
+    QString m_name;
+    QHash<QString, QString> replaceRules;
+};
 
-    QString code() const;
+using CodeSnipFragment = std::variant<QString, TemplateInstance>;
 
-    TemplateInstancePtr instance() const { return m_instance; }
+size_t qHash(const CodeSnipFragment &codeFrag, size_t seed = 0) noexcept;
 
-private:
-    QString m_code;
-    std::shared_ptr<TemplateInstance> m_instance;
-};
+QDebug operator<<(QDebug d, const CodeSnipFragment &codeFrag);
 
 class CodeSnipAbstract : public CodeSnipHelpers
 {
 public:
+    using CodeSnipFragments = QList<CodeSnipFragment>;
+
     QString code() const;
 
     void addCode(const QString &code);
     void addCode(QStringView code) { addCode(code.toString()); }
 
-    void addTemplateInstance(const TemplateInstancePtr &ti)
+    void addTemplateInstance(const TemplateInstance &ti)
     {
-        codeList.append(CodeSnipFragment(ti));
+        m_codeList.emplace_back(CodeSnipFragment{ti});
     }
 
-    bool isEmpty() const { return codeList.isEmpty(); }
+    bool isEmpty() const { return m_codeList.empty(); }
     void purgeEmptyFragments();
 
-    QList<CodeSnipFragment> codeList;
+    const CodeSnipFragments &codeList() const { return m_codeList; }
 
     static QRegularExpression placeHolderRegex(int index);
+
+private:
+    CodeSnipFragments m_codeList;
 };
 
 class TemplateEntry : public CodeSnipAbstract
 {
 public:
-    explicit TemplateEntry(const QString &name) : m_name(name) {}
-
-    QString name() const
-    {
-        return m_name;
-    }
+    const QString &name() const { return m_name; }
+    void setName(const QString &n) { m_name = n ;}
 
 private:
     QString m_name;
@@ -98,6 +93,12 @@ public:
 
     TypeSystem::Language language = TypeSystem::TargetLangCode;
     TypeSystem::CodeSnipPosition position = TypeSystem::CodeSnipPositionAny;
+
+    Q_DECLARE_EQUALITY_COMPARABLE(CodeSnip)
+
+    friend bool comparesEqual(const CodeSnip &lhs, const CodeSnip &rhs) noexcept;
+    friend size_t qHash(const CodeSnip &s, size_t seed = 0) noexcept
+    { return qHashMulti(seed, s.position, s.language, s.codeList()); }
 };
 
 /// Purge empty fragments and snippets caused by new line characters in
index 775cf10aff6829b662a4130340c91c2978b9f87e..9d16cb44fc6d3a89a1addaf07263acd050ac1456 100644 (file)
@@ -3,7 +3,7 @@
 
 #include "codesniphelpers.h"
 
-#include <QtCore/QStringList>
+#include <QtCore/qstringlist.h>
 
 #include <algorithm>
 
index e7a7545da87a94ebea8dbb1048c8afabf2cd0ac4..724e397e6b7925151f569e594394a9741ff57369 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef CODESNIPHELPERS_H
 #define CODESNIPHELPERS_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 class CodeSnipHelpers
 {
index f1d711313ef2d5d285894e0269fc0c8312865c43..01d204ec5a1fa85378c6fee9cf40a24ba1da11d5 100644 (file)
@@ -9,7 +9,7 @@
 #include "modifications_typedefs.h"
 #include "pymethoddefentry.h"
 
-#include <QtCore/QSet>
+#include <QtCore/qset.h>
 
 class ComplexTypeEntryPrivate;
 
@@ -48,12 +48,6 @@ public:
     };
     Q_DECLARE_FLAGS(TypeFlags, TypeFlag)
 
-    enum CopyableFlag {
-        CopyableSet,
-        NonCopyableSet,
-        Unknown
-    };
-
     explicit ComplexTypeEntry(const QString &entryName, Type t, const QVersionNumber &vr,
                               const TypeEntryCPtr &parent);
 
@@ -129,12 +123,6 @@ public:
     bool deleteInMainThread() const;
     void setDeleteInMainThread(bool d);
 
-    CopyableFlag copyable() const;
-    void setCopyable(CopyableFlag flag);
-
-    TypeSystem::QtMetaTypeRegistration qtMetaTypeRegistration() const;
-    void setQtMetaTypeRegistration(TypeSystem::QtMetaTypeRegistration r);
-
     QString hashFunction() const;
     void setHashFunction(const QString &hashFunction);
 
@@ -148,10 +136,6 @@ public:
     TypeSystem::AllowThread allowThread() const;
     void setAllowThread(TypeSystem::AllowThread allowThread);
 
-    QString defaultConstructor() const;
-    void setDefaultConstructor(const QString& defaultConstructor);
-    bool hasDefaultConstructor() const;
-
     TypeEntry *clone() const override;
 
     void useAsTypedef(const ComplexTypeEntryCPtr &source);
@@ -159,10 +143,6 @@ public:
     TypeSystem::SnakeCase snakeCase() const;
     void setSnakeCase(TypeSystem::SnakeCase sc);
 
-    // Determined by AbstractMetaBuilder from the code model.
-    bool isValueTypeWithCopyConstructorOnly() const;
-    void setValueTypeWithCopyConstructorOnly(bool v);
-
     QString docFile() const;
     void setDocFile(const QString &docFile);
 
index 18b4488c3a45908fd5f7375010c1e29b83c31956..6b9f0ae2ec16c421d5e671d8efe8423808c26da6 100644 (file)
@@ -3,8 +3,8 @@
 
 #include "conditionalstreamreader.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QHash>
+#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
 
 using namespace Qt::StringLiterals;
 
index 73069752506487a9c652d11f01e8026d569578ea..36c4752a432fa4a9e3bcf6515501047bada2a512 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef CONDITIONALSTREAMREADER_H
 #define CONDITIONALSTREAMREADER_H
 
-#include <QtCore/QXmlStreamReader>
+#include <QtCore/qxmlstream.h>
 
 #include <utility>
 
index 59522e16c9993dd826cc1c1eab9a7c36986bdc26..731a663e8bb6dd080c7c08eb39ba1e647a6605d3 100644 (file)
@@ -4,11 +4,11 @@
 #ifndef CONFIGURABLETYPEENTRY_H
 #define CONFIGURABLETYPEENTRY_H
 
-#include "typesystem.h"
+#include "cpptypeentry.h"
 
 class ConfigurableTypeEntryPrivate;
 
-class ConfigurableTypeEntry : public TypeEntry
+class ConfigurableTypeEntry : public CppTypeEntry
 {
 public:
     explicit ConfigurableTypeEntry(const QString &entryName, Type t,
diff --git a/sources/shiboken6/ApiExtractor/cpptypeentry.h b/sources/shiboken6/ApiExtractor/cpptypeentry.h
new file mode 100644 (file)
index 0000000..5363b8b
--- /dev/null
@@ -0,0 +1,59 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CPPTYPEENTRY_H
+#define CPPTYPEENTRY_H
+
+#include "typesystem.h"
+#include "typesystem_enums.h"
+
+class CppTypeEntryPrivate;
+
+class CppTypeEntry : public TypeEntry
+{
+public:
+    explicit CppTypeEntry(const QString &entryName, Type t, const QVersionNumber &vr,
+                          const TypeEntryCPtr &parent);
+
+    const QString &defaultConstructor() const;
+    void setDefaultConstructor(const QString& defaultConstructor);
+    bool hasDefaultConstructor() const { return !defaultConstructor().isEmpty(); }
+
+    // View on: Type to use for function argument conversion, fex
+    // "std::string_view" -> "std::string" for "foo(std::string_view)".
+    // cf AbstractMetaType::viewOn()
+    CppTypeEntryCPtr viewOn() const;
+    void setViewOn(const CppTypeEntryCPtr &v);
+
+    // Typesystem specification, potentially overriding the code model detection
+    bool isDefaultConstructible() const;
+    bool isCopyable() const;
+    bool isMovable() const;
+    bool isMoveOnlyType() const { return !isCopyable() && isMovable(); }
+
+    // Parser/code model interface
+    TypeSystem::DefaultConstructibleFlag defaultConstructibleFlag() const;
+    void setDefaultConstructibleFlag(TypeSystem::DefaultConstructibleFlag flag);
+    void setDefaultConstructibleDetected(bool c); // set value detected by code model
+
+    TypeSystem::CopyableFlag copyableFlag() const;
+    void setCopyableFlag(TypeSystem::CopyableFlag flag);
+    void setCopyableDetected(bool c); // set value detected by code model
+
+    TypeSystem::MovableFlag movableFlag() const;
+    void setMovableFlag(TypeSystem::MovableFlag flag);
+
+    TypeSystem::QtMetaTypeRegistration qtMetaTypeRegistration() const;
+    void setQtMetaTypeRegistration(TypeSystem::QtMetaTypeRegistration r);
+
+    TypeEntry *clone() const override;
+
+#ifndef QT_NO_DEBUG_STREAM
+    void formatDebug(QDebug &debug) const override;
+#endif
+
+protected:
+    explicit CppTypeEntry(CppTypeEntryPrivate *d);
+};
+
+#endif // CPPTYPEENTRY_H
index fd0a67759b89ddd840e5fe1e560f0105dd02db54..a751788464714f2cd360defe9281215bca5bf82b 100644 (file)
@@ -7,8 +7,8 @@
 #include "customconversion_typedefs.h"
 #include "typesystem_typedefs.h"
 
-#include <QtCore/QList>
-#include <QtCore/QString>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
 
 QT_FORWARD_DECLARE_CLASS(QDebug)
 
index 6528f7d7b6c9743d638833b9ca27b18869b99037..1e5cf4bb99b76fc9ff2c8fbc2c1b92836f9ffa07 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef CUSTOMCONVERSION_TYPEDEFS_H
 #define CUSTOMCONVERSION_TYPEDEFS_H
 
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
 
 #include <memory>
 
index a57bb858f6db1f408b14c06480b04dfe4e93b958..49b19f9d8ce6fbe8cbc37febb2817f3fc6b7acdb 100644 (file)
@@ -4,9 +4,11 @@
 #ifndef CUSTOMTYPENENTRY_H
 #define CUSTOMTYPENENTRY_H
 
-#include "typesystem.h"
+#include "cpptypeentry.h"
 
-class CustomTypeEntry : public TypeEntry
+class CustomTypeEntryPrivate;
+
+class CustomTypeEntry : public CppTypeEntry
 {
 public:
     explicit CustomTypeEntry(const QString &entryName, const QVersionNumber &vr,
@@ -23,7 +25,7 @@ public:
 #endif
 
 protected:
-    explicit CustomTypeEntry(TypeEntryPrivate *d);
+    explicit CustomTypeEntry(CustomTypeEntryPrivate *d);
 };
 
 
index 81ebbb3b9f9caec5969e6ff77d09caf72a9217aa..b9ceaf524a6edbbbc56645f791632e148fbd07c2 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef DEBUGHELPERS_P_H
 #define DEBUGHELPERS_P_H
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 #include <memory>
 
 template <class T>
index aa280de032105274615e09f3da0cd50fa5d64590..000f0b23ebe6ed3c771d30090350676a7e756768 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef DEPENDENCY_H
 #define DEPENDENCY_H
 
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
 
 #include <utility>
 
index 468fe10981d6bf76c230802b9930aac4530ade19..f3c51085af044ceda95a115e1ca31a781d783a74 100644 (file)
 #include "complextypeentry.h"
 #include "xmlutils.h"
 
-#include <QtCore/QBuffer>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QTextStream>
+#include <QtCore/qbuffer.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qtextstream.h>
 
 #include "qtcompat.h"
 
 #include <cstdlib>
-#ifdef HAVE_LIBXSLT
-#  include <libxslt/xsltutils.h>
-#  include <libxslt/transform.h>
-#endif
 
 #include <algorithm>
 
index da3ca8cdadbff07bff99c1af9e410ea967fef965..dd1ea79794f2e538725c6ed8b36e28744341bfc9 100644 (file)
@@ -7,7 +7,7 @@
 #include "modifications_typedefs.h"
 #include "documentation.h"
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 #include <memory>
 
index 7d9054af753bbfe9dfce87144fb80735a8c43332..310b4e2b76f71042663a37c68db455efa1d855ad 100644 (file)
@@ -3,7 +3,7 @@
 
 #include "documentation.h"
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 Documentation::Documentation(const QString &detailed,
                              const QString &brief,
index 26e7b22ae03442420124d19894f0e11389462c63..580d8f969a21ba55deb9f94c92d1df42dec765cb 100644 (file)
@@ -6,8 +6,8 @@
 
 #include "documentation_enums.h"
 
-#include <QtCore/QString>
-#include <QtCore/QtCompare>
+#include <QtCore/qstring.h>
+#include <QtCore/qcompare.h>
 
 QT_FORWARD_DECLARE_CLASS(QDebug)
 
index 8aeded53a368b362f9c721c0781a4e7f0afabcb8..35a0acfdbe1d64073aec7a2e5b6102ff6b61123c 100644 (file)
@@ -4,7 +4,13 @@
 #ifndef DOCUMENTATION_ENUMS_H
 #define DOCUMENTATION_ENUMS_H
 
-#include <QtCore/QtTypes>
+#include <QtCore/qtypes.h>
+
+enum class DocumentationTarget : uint8_t
+{
+    Documentation, // Documentation
+    DocString // Python doc string (binding code)
+};
 
 enum class DocumentationFormat : uint8_t
 {
index 0bd192257ece89e4eeea304e25a5461a1af8f39f..54c75410a0afe126b0da043b8d5d875b515d5367 100644 (file)
@@ -3,11 +3,11 @@
 
 #include "dotview.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QProcess>
-#include <QtCore/QTemporaryFile>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qtemporaryfile.h>
 
 using namespace Qt::StringLiterals;
 
index 87fb7db656d889671c7ecd5ba40f090fa5038b3e..41cf66ce2764fefa5cd60a2a812fdbc6d0b92275 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef DOTVIEW_H
 #define DOTVIEW_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 /// Show a dot digraph in an image viewer
 /// \param name base name for files
index e3742cab0ac6b11812fb48ef66ea6127219039ed..fb76e9c62b1b945ff179934b537a1039b8ac329d 100644 (file)
@@ -18,8 +18,8 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QFile>
-#include <QtCore/QDir>
+#include <QtCore/qfile.h>
+#include <QtCore/qdir.h>
 
 using namespace Qt::StringLiterals;
 
index 396b56f5d6fd0aa6cdb33ad29ac7eccc2e226a31..5d65633b65d6ac9c7b8ebc7a664feca986d20d92 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef EXCEPTION_H
 #define EXCEPTION_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 #include <string>
 #include <exception>
diff --git a/sources/shiboken6/ApiExtractor/filecache.cpp b/sources/shiboken6/ApiExtractor/filecache.cpp
new file mode 100644 (file)
index 0000000..0e54c6a
--- /dev/null
@@ -0,0 +1,142 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "filecache.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qregularexpression.h>
+
+#include <algorithm>
+
+using namespace Qt::StringLiterals;
+
+constexpr qsizetype MAX_CACHE_SIZE = 20;
+
+static QString msgCannotFindSnippet(const QString &file, const QString &snippetLabel)
+{
+    return "Cannot find snippet \""_L1  + snippetLabel + "\" in \""_L1
+           + QDir::toNativeSeparators(file) + "\"."_L1;
+}
+
+static QString msgUnterminatedSnippet(const QString &file, const QString &snippetLabel)
+{
+    return "Snippet \""_L1  + snippetLabel + "\" in \""_L1
+           + QDir::toNativeSeparators(file) + "\" is not terminated."_L1;
+}
+
+static QString msgCannotOpenFileForReading(const QFile &f)
+{
+    return "Failed to open file \""_L1 + QDir::toNativeSeparators(f.fileName())
+           + "\" for reading: "_L1 +f.errorString();
+}
+
+std::optional<QString> FileCache::fileContents(const QString &name)
+{
+    const qsizetype index = ensureEntry(name);
+    if (index == -1)
+        return std::nullopt;
+    return m_cache.at(index).contents;
+}
+
+void FileCache::ensureLines(FileCacheEntry *entry)
+{
+    if (entry->lines.isEmpty())
+        entry->lines = QStringView{entry->contents}.split(u'\n');
+}
+
+std::optional<FileCache::Lines> FileCache::lines(const QString &name)
+{
+    const qsizetype index = ensureEntry(name);
+    if (index == -1)
+        return std::nullopt;
+    FileCacheEntry &entry = m_cache[index];
+    ensureLines(&entry);
+    return entry.lines;
+}
+
+std::optional<QString> FileCache::fileSnippet(const QString &name,
+                                              const QString &snippetName,
+                                              const QRegularExpression &snippetPattern)
+{
+    const qsizetype index = ensureEntry(name);
+    if (index == -1)
+        return std::nullopt;
+    FileCacheEntry &entry = m_cache[index];
+    ensureLines(&entry);
+
+    // Check for a comment line and the snippet ID
+    auto pred = [&snippetPattern](QStringView line) {
+        return (line.contains(u'/') || line.contains(u'#'))
+               && snippetPattern.matchView(line).hasMatch(); };
+
+    const auto end = entry.lines.cend();
+    const auto i1 = std::find_if(entry.lines.cbegin(), end, pred);
+    if (i1 == end) {
+        m_error = msgCannotFindSnippet(name, snippetName);
+        return std::nullopt;
+    }
+
+    auto pos = i1;
+    const auto i2 = std::find_if(++pos, end, pred);
+    if (i2 == end) {
+        m_error = msgUnterminatedSnippet(name, snippetName);
+        return std::nullopt;
+    }
+
+    const QChar *startSnippet = i1->constData() + i1->size() + 1;
+    const auto snippetSize = i2->constData() - startSnippet;
+    const auto startSnippetIndex = startSnippet - entry.lines.cbegin()->constData();
+    return entry.contents.sliced(startSnippetIndex, snippetSize);
+}
+
+qsizetype FileCache::ensureEntry(const QString &name)
+{
+    const qsizetype index = indexOf(name);
+    if (index != -1) {
+        ++m_hits;
+        return index;
+    }
+
+    ++m_misses;
+    m_error.clear();
+    QFile file(name);
+    if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) {
+        m_error = msgCannotOpenFileForReading(file);
+        return -1;
+    }
+
+    QString contents = QString::fromUtf8(file.readAll());
+    m_cache.prepend({name, contents, {}});
+    while (m_cache.size() >= MAX_CACHE_SIZE)
+        m_cache.removeLast();
+    return 0;
+}
+
+qsizetype FileCache::indexOf(const QString &name) const
+{
+    for (qsizetype i = 0, size = m_cache.size(); i < size; ++i) {
+        if (m_cache.at(i).name == name)
+            return i;
+    }
+    return -1;
+}
+
+void FileCache::formatDebug(QDebug &debug) const
+{
+    debug << "FileCache(" << m_cache.size() << " entries, "
+          << m_hits << " hits, " << m_misses << " misses [";
+    for (const auto &e : m_cache)
+        debug << QDir::toNativeSeparators(e.name) << ' ' << e.contents.size() << "B ";
+    debug << "])";
+}
+
+QDebug operator<<(QDebug debug, const FileCache &c)
+{
+    QDebugStateSaver saver(debug);
+    debug.noquote();
+    debug.nospace();
+    c.formatDebug(debug);
+    return debug;
+}
diff --git a/sources/shiboken6/ApiExtractor/filecache.h b/sources/shiboken6/ApiExtractor/filecache.h
new file mode 100644 (file)
index 0000000..eca46f7
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef FILECACHE_H
+#define FILECACHE_H
+
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringview.h>
+
+#include <optional>
+
+QT_FORWARD_DECLARE_CLASS(QRegularExpression)
+QT_FORWARD_DECLARE_CLASS(QDebug)
+
+// Queue-based cache for the contents of a number of recent files with a
+// convenience API for retrieving lines and regexp-delimited snippets.
+class FileCache
+{
+public:
+    using Lines = QList<QStringView>;
+
+    std::optional<QString> fileContents(const QString &name);
+    std::optional<Lines> lines(const QString &name);
+    std::optional<QString> fileSnippet(const QString &name,
+                                       const QString &snippetName,
+                                       const QRegularExpression &snippetPattern);
+
+    const QString &errorString() const { return m_error; }
+
+    void formatDebug(QDebug &debug) const;
+
+private:
+    struct FileCacheEntry
+    {
+        QString name;
+        QString contents;
+        Lines lines;
+    };
+
+    qsizetype ensureEntry(const QString &name);
+    qsizetype indexOf(const QString &name) const;
+    static void ensureLines(FileCacheEntry *entry);
+
+    QList<FileCacheEntry> m_cache;
+    QString m_error;
+    int m_hits = 0;
+    int m_misses = 0;
+};
+
+QDebug operator<<(QDebug debug, const FileCache &c);
+
+#endif // FILECACHE_H
index 6f9ec4d8a378c5e91e0273d92ea18ae82ddd0689..2aa7a549748a7876b0f1b486646225e914411e1f 100644 (file)
@@ -6,9 +6,9 @@
 #include "reporthandler.h"
 #include "exception.h"
 
-#include <QtCore/QFileInfo>
-#include <QtCore/QDir>
-#include <QtCore/QDebug>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qdebug.h>
 
 #include <cstdio>
 
index 6eddcd12b6927158705d23d686ba3f56e06346d9..d71dbfab173761eff5ad889ab163c9e35a33f7a9 100644 (file)
@@ -4,13 +4,13 @@
 #ifndef FLAGSTYPEENTRY_H
 #define FLAGSTYPEENTRY_H
 
-#include "typesystem.h"
+#include "cpptypeentry.h"
 
 class EnumTypeEntry;
 class FlagsTypeEntryPrivate;
 
 // FlagsTypeEntry is configurable for global flags only
-class FlagsTypeEntry : public TypeEntry
+class FlagsTypeEntry : public CppTypeEntry
 {
 public:
     explicit FlagsTypeEntry(const QString &entryName, const QVersionNumber &vr,
index 22936c201f1940c948d00ca511afc210e341a66c..db5ba802e4781fc6ebbd87800bc684bf1ea5bfd0 100644 (file)
@@ -6,12 +6,12 @@
 
 #include "dotview.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QFile>
-#include <QtCore/QHash>
-#include <QtCore/QList>
-#include <QtCore/QString>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qtextstream.h>
 
 #include <algorithm>
 
index af4a768e8afffd36b37f3e3ac1013bc19bfbf3fb..f8845b78553c9547202437935731a1aff2f202c3 100644 (file)
@@ -4,8 +4,8 @@
 #ifndef HEADER_PATHS_H
 #define HEADER_PATHS_H
 
-#include <QtCore/QByteArray>
-#include <QtCore/QList>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qlist.h>
 
 enum class HeaderType
 {
index aee6b73379502cbcf85e2c3bcc624e57071a846f..fec1136b85282b09a816221150ab8260353eb981 100644 (file)
@@ -4,10 +4,10 @@
 #include "include.h"
 #include "textstream.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QHash>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qtextstream.h>
 
 #include "qtcompat.h"
 
index 875a941f9149c1e9d0d9e8f62c04cb2267302be9..1bba39a094fc20c10758de54957dea2c5cab6ec0 100644 (file)
@@ -4,10 +4,10 @@
 #ifndef INCLUDE_H
 #define INCLUDE_H
 
-#include <QtCore/QtCompare>
-#include <QtCore/QHashFunctions>
-#include <QtCore/QString>
-#include <QtCore/QList>
+#include <QtCore/qcompare.h>
+#include <QtCore/qhashfunctions.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qlist.h>
 
 QT_BEGIN_NAMESPACE
 class QTextStream;
@@ -47,7 +47,7 @@ public:
     int compare(const Include &rhs) const;
 
 private:
-    friend size_t qHash(Include &inc, size_t seed = 0) noexcept
+    friend size_t qHash(const Include &inc, size_t seed = 0) noexcept
     {
         return qHashMulti(seed, inc.m_type, inc.m_name);
     }
index 57b4e75751fb46deb39859473fa45071ec933802..5647cccc5fb964cf9d86b375245a67c3653a0dc3 100644 (file)
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QStringList>
-#include <QtCore/QXmlStreamReader>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qxmlstream.h>
 
 #include <algorithm>
 #include <iterator>
@@ -46,6 +47,20 @@ static QTextStream &operator<<(QTextStream &s, Access a)
     return s;
 }
 
+QString msgModificationCandidates(const AbstractMetaFunctionCPtr &function)
+{
+    QString result;
+    const auto &signatures = function->modificationSignatures();
+    for (qsizetype i = 0, size = signatures.size(); i < size; ++i) {
+        if (i > 0)
+            result += " / "_L1;
+        result += u'"' + signatures.at(i) + u'"';
+    }
+    if (auto klazz = function->implementingClass())
+        result += " in "_L1 + klazz->name();
+    return result;
+}
+
 QString msgNoFunctionForModification(const AbstractMetaClassCPtr &klass,
                                      const QString &signature,
                                      const QString &originalSignature,
@@ -75,6 +90,19 @@ QString msgNoFunctionForModification(const AbstractMetaClassCPtr &klass,
     return result;
 }
 
+QString msgModificationConstMismatch(const AbstractMetaFunctionCPtr &function,
+                                     const QString &modificationSignature)
+{
+    QString result;
+    QTextStream str(&result);
+    if (auto klazz = function->implementingClass())
+        str << klazz->typeEntry()->sourceLocation();
+    str << "signature \"" << modificationSignature
+        << "\" needs \"const\" to fully match \"" << function->classQualifiedSignature()
+        << "\". The partial matching will be removed in a future release.";
+    return result;
+}
+
 QString msgArgumentIndexOutOfRange(const AbstractMetaFunction *func, int index)
 {
     QString result;
@@ -903,15 +931,6 @@ QString msgCannotFindView(const QString &viewedName, const QString &name)
         + u" for "_s + name;
 }
 
-QString msgCannotFindSnippet(const QString &file, const QString &snippetLabel)
-{
-    QString result;
-    QTextStream str(&result);
-    str << "Cannot find snippet \"" << snippetLabel << "\" in "
-        << QDir::toNativeSeparators(file) << '.';
-    return result;
-}
-
 QString msgSnippetError(const QString &context, const char *what)
 {
     return "Error processing code snippet of "_L1 + context
@@ -1053,3 +1072,45 @@ QString msgCannotFindQDocFile(const AbstractMetaClassCPtr &metaClass,
         << "), tried: " << nativeCandidates.join(", "_L1);
     return result;
 }
+
+QString msgCannotCall(const AbstractMetaFunctionCPtr &func,
+                      int arg, bool injectCodeCallsFunc, bool hasConversionRule)
+{
+    QString result;
+    QTextStream str(&result);
+    str << "No way to generate a binding call for \"" << func->ownerClass()->name() << "::"
+        << func->signature() << '"';
+    if (func->isUserAdded())
+        str << " (user added)";
+    str << " with the modifications for argument " << (arg + 1) << ':';
+    if (!injectCodeCallsFunc)
+        str << " There is no code injection calling the function.";
+    if (!hasConversionRule)
+        str << " There is no conversion rule.";
+    return result;
+}
+
+QString msgRemoveRedundantOverload(const AbstractMetaFunctionCPtr &func,
+                                   const QString &type)
+{
+    return "Removing \""_L1 + func->classQualifiedSignature()
+           + "\" due to presence of an overload taking a \""_L1
+           + type + "\" parameter."_L1;
+}
+
+QString msgCommandLineArguments(const QStringList &argv)
+{
+    QString result = "Host platform: "_L1 + QLatin1StringView(QLibraryInfo::build())
+                     + "\nCommand line:\n   "_L1;
+    for (const QString &arg : argv) {
+        result.append(u' ');
+        const bool quote = arg.contains(u' ');
+        if (quote)
+            result.append(u'"');
+        result.append(arg);
+        if (quote)
+            result.append(u'"');
+    }
+    result.append(u'\n');
+    return result;
+}
index a7d491db1ac26d2814aa27d66e7753caf17bede7..465c457b856b172c4a614c2b376ab8be9684dde9 100644 (file)
@@ -9,7 +9,7 @@
 #include "modifications_typedefs.h"
 #include "typesystem_typedefs.h"
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 class EnumTypeEntry;
 class FunctionTypeEntry;
@@ -37,12 +37,17 @@ QString msgUnnamedArgumentDefaultExpression(const AbstractMetaClassCPtr &context
 
 QString msgArgumentIndexOutOfRange(const AbstractMetaFunction *func, int index);
 
+QString msgModificationCandidates(const AbstractMetaFunctionCPtr &function);
+
 QString msgNoFunctionForModification(const AbstractMetaClassCPtr &klass,
                                      const QString &signature,
                                      const QString &originalSignature,
                                      const QStringList &possibleSignatures,
                                      const AbstractMetaFunctionCList &allFunctions);
 
+QString msgModificationConstMismatch(const AbstractMetaFunctionCPtr &function,
+                                     const QString &modificationSignature);
+
 QString msgTypeModificationFailed(const QString &type, int n,
                                   const AbstractMetaFunction *func,
                                   const QString &why);
@@ -215,7 +220,6 @@ QString msgIncorrectlyNestedName(const QString &name);
 
 QString msgCannotFindView(const QString &viewedName, const QString &name);
 
-QString msgCannotFindSnippet(const QString &file, const QString &snippetLabel);
 QString msgSnippetError(const QString &context, const char *what);
 QString msgUnableToResolveTypedef(const QString &sourceType, const QString &sourceName);
 
@@ -276,4 +280,12 @@ QString msgCannotCopy(const QFile &source, const QString &target);
 QString msgCannotFindQDocFile(const AbstractMetaClassCPtr &metaClass,
                               const QStringList &candidates);
 
+QString msgCannotCall(const AbstractMetaFunctionCPtr &func,
+                      int arg, bool injectCodeCallsFunc, bool hasConversionRule);
+
+QString msgRemoveRedundantOverload(const AbstractMetaFunctionCPtr &func,
+                                   const QString &type);
+
+QString msgCommandLineArguments(const QStringList &argv);
+
 #endif // MESSAGES_H
index d876e8035463bd23beadc59476a130c58d0db047..866fd0b6d83f6776e5d87fcb77d96cb4093c76f1 100644 (file)
@@ -6,8 +6,8 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QRegularExpression>
+#include <QtCore/qdebug.h>
+#include <QtCore/qregularexpression.h>
 
 #include <algorithm>
 #include <limits>
@@ -166,27 +166,14 @@ QDebug operator<<(QDebug d, const CodeSnip &s)
     QDebugStateSaver saver(d);
     d.noquote();
     d.nospace();
-    const auto size = s.codeList.size();
+    const auto &fragments = s.codeList();
+    const auto size = fragments.size();
     d << "CodeSnip(language=" << s.language << ", position=" << s.position
         << ", fragments[" << size << "]=";
     for (qsizetype i = 0; i < size; ++i) {
-        const auto &f = s.codeList.at(i);
         if (i)
             d << ", ";
-        d << '#' << i << ' ';
-        if (!f.instance()) {
-            d << '"';
-            const QString &code = f.code();
-            const auto lines = QStringView{code}.split(u'\n');
-            for (qsizetype i = 0, size = lines.size(); i < size; ++i) {
-                if (i)
-                    d << "\\n";
-                d << lines.at(i).trimmed();
-            }
-            d << '"';
-        } else {
-            d << "template=\"" << f.instance()->name() << '"';
-        }
+        d << '#' << i << ' ' << fragments.at(i);
     }
     d << ')';
     return d;
@@ -200,6 +187,9 @@ public:
         noNullPointers(false), resetAfterUse(false), array(false)
     {}
 
+    bool equals(const ArgumentModificationData &rhs) const noexcept;
+    size_t hash(size_t seed) const noexcept;
+
     QList<ReferenceCount> referenceCounts;
     QString modified_type;
     QString pyiType;
@@ -217,6 +207,33 @@ public:
     uint array : 1;
 };
 
+bool ArgumentModificationData::equals(const ArgumentModificationData &rhs) const noexcept
+{
+    return referenceCounts == rhs.referenceCounts
+        && modified_type == rhs.modified_type
+        && pyiType == rhs.pyiType
+        && replacedDefaultExpression == rhs.replacedDefaultExpression
+        && m_targetOwnerShip == rhs.m_targetOwnerShip
+        && m_nativeOwnerShip == rhs.m_nativeOwnerShip
+        && conversion_rules == rhs.conversion_rules
+        && owner == rhs.owner
+        && renamed_to == rhs.renamed_to
+        && index == rhs.index
+        && removedDefaultExpression == rhs.removedDefaultExpression
+        && removed == rhs.removedDefaultExpression
+        && noNullPointers == rhs.noNullPointers
+        && resetAfterUse == rhs.resetAfterUse
+        && array == rhs.array;
+}
+
+size_t ArgumentModificationData::hash(size_t seed) const noexcept
+{
+    return qHashMulti(seed, referenceCounts, modified_type, pyiType,replacedDefaultExpression,
+                      m_targetOwnerShip, m_nativeOwnerShip, conversion_rules, owner,
+                      renamed_to, index, removedDefaultExpression, removed, noNullPointers,
+                      resetAfterUse, array);
+}
+
 ArgumentModification::ArgumentModification() : d(new ArgumentModificationData)
 {
 }
@@ -398,9 +415,22 @@ void ArgumentModification::addReferenceCount(const ReferenceCount &value)
     d->referenceCounts.append(value);
 }
 
+bool ArgumentModification::equals(const ArgumentModification &rhs) const noexcept
+{
+    return d.constData() == rhs.d.constData() || d->equals(*rhs.d);
+}
+
+size_t ArgumentModification::hash(size_t seed) const noexcept
+{
+    return d->hash(seed);
+}
+
 class FunctionModificationData : public QSharedData
 {
 public:
+    bool equals(const FunctionModificationData &rhs) const noexcept;
+    size_t hash(size_t seed) const noexcept;
+
     QString renamedToName;
     FunctionModification::Modifiers modifiers;
     CodeSnipList m_snips;
@@ -410,11 +440,36 @@ public:
     QRegularExpression m_signaturePattern;
     int m_overloadNumber = TypeSystem::OverloadNumberUnset;
     bool removed = false;
+    bool inherited = false;
     TypeSystem::AllowThread m_allowThread = TypeSystem::AllowThread::Unspecified;
     TypeSystem::ExceptionHandling m_exceptionHandling = TypeSystem::ExceptionHandling::Unspecified;
     TypeSystem::SnakeCase snakeCase = TypeSystem::SnakeCase::Unspecified;
 };
 
+bool FunctionModificationData::equals(const FunctionModificationData &rhs) const noexcept
+{
+    return renamedToName == rhs.renamedToName
+        && modifiers == rhs.modifiers
+        && m_snips == rhs.m_snips
+        && m_argument_mods == rhs.m_argument_mods
+        && m_signature == rhs.m_signature
+        && m_originalSignature == rhs.m_originalSignature
+        && m_signaturePattern == rhs.m_signaturePattern
+        && m_overloadNumber == rhs.m_overloadNumber
+        && removed == rhs.removed
+        && inherited == rhs.inherited
+        && m_allowThread == rhs.m_allowThread
+        && m_exceptionHandling == rhs.m_exceptionHandling
+        && snakeCase == rhs.snakeCase;
+}
+
+size_t FunctionModificationData::hash(size_t seed) const noexcept
+{
+    return qHashMulti(seed, renamedToName, modifiers, m_snips, m_argument_mods, m_signature,
+                      m_originalSignature, m_signaturePattern, m_overloadNumber, removed,
+                      inherited, m_allowThread, m_exceptionHandling, snakeCase);
+}
+
 FunctionModification::FunctionModification() : d(new FunctionModificationData)
 {
 }
@@ -425,6 +480,16 @@ FunctionModification::FunctionModification(FunctionModification &&) noexcept = d
 FunctionModification &FunctionModification::operator=(FunctionModification &&) noexcept = default;
 FunctionModification::~FunctionModification() = default;
 
+bool FunctionModification::equals(const FunctionModification &rhs) const noexcept
+{
+    return d.constData() == rhs.d.constData() || d->equals(*rhs.d);
+}
+
+size_t FunctionModification::hash(size_t seed) const noexcept
+{
+    return d->hash(seed);
+}
+
 void FunctionModification::formatDebug(QDebug &debug) const
 {
     if (d->m_signature.isEmpty())
@@ -493,6 +558,17 @@ void FunctionModification::setRemoved(bool r)
         d->removed = r;
 }
 
+bool FunctionModification::isInherited() const
+{
+    return d->inherited;
+}
+
+void FunctionModification::setInherited(bool i)
+{
+    if (d->inherited != i)
+        d->inherited = i;
+}
+
 const QList<ArgumentModification> &FunctionModification::argument_mods() const
 {
     return d->m_argument_mods;
@@ -556,11 +632,9 @@ bool FunctionModification::matches(const QStringList &functionSignatures) const
     if (!d->m_signature.isEmpty())
         return functionSignatures.contains(d->m_signature);
 
-    for (const auto &s : functionSignatures) {
-        if (d->m_signaturePattern.match(s).hasMatch())
-            return true;
-    }
-    return false;
+    const auto &pattern = d->m_signaturePattern;
+    return std::any_of(functionSignatures.cbegin(), functionSignatures.end(),
+                       [&pattern] (const QString &s) { return pattern.match(s).hasMatch(); });
 }
 
 bool FunctionModification::setSignature(const QString &s, QString *errorMessage)
index 4db5836a729328a39f455129caf42de2a1ba0cef..ca130104e5f55214094d27cde0f665c3566ef922 100644 (file)
@@ -8,9 +8,9 @@
 #include "documentation_enums.h"
 #include "modifications_typedefs.h"
 
-#include <QtCore/QList>
-#include <QtCore/QSharedDataPointer>
-#include <QtCore/QString>
+#include <QtCore/qlist.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qstring.h>
 
 class ArgumentModificationData;
 class CodeSnip;
@@ -38,6 +38,13 @@ struct ReferenceCount
 
     QString varName;
     Action action;
+
+    Q_DECLARE_EQUALITY_COMPARABLE(ReferenceCount)
+
+    friend bool comparesEqual(const ReferenceCount &lhs, const ReferenceCount &rhs) noexcept
+    {  return lhs.action == rhs.action && lhs.varName == rhs.varName; }
+    friend size_t qHash(const ReferenceCount &r, size_t seed = 0) noexcept
+    { return qHashMulti(seed, r.action, r.varName); }
 };
 
 struct ArgumentOwner
@@ -56,6 +63,13 @@ struct ArgumentOwner
 
     Action action = Invalid;
     int index = InvalidIndex;
+
+    Q_DECLARE_EQUALITY_COMPARABLE(ArgumentOwner)
+
+    friend bool comparesEqual(const ArgumentOwner &lhs, const ArgumentOwner &rhs) noexcept
+    {  return lhs.action == rhs.action && lhs.index == rhs.index; }
+    friend size_t qHash(const ArgumentOwner &a, size_t seed = 0) noexcept
+    { return qHashMulti(seed, a.action, a.index); }
 };
 
 class ArgumentModification
@@ -124,6 +138,16 @@ public:
     void setArray(bool value);
 
 private:
+    bool equals(const ArgumentModification &rhs) const noexcept;
+    size_t hash(size_t seed) const noexcept;
+
+    Q_DECLARE_EQUALITY_COMPARABLE(ArgumentModification)
+
+    friend bool comparesEqual(const ArgumentModification &lhs, const ArgumentModification &rhs) noexcept
+    {  return lhs.equals(rhs); }
+    friend size_t qHash(const ArgumentModification &a, size_t seed = 0) noexcept
+    { return a.hash(seed); }
+
     QSharedDataPointer<ArgumentModificationData> d;
 };
 
@@ -171,6 +195,9 @@ public:
     bool isRemoved() const;
     void setRemoved(bool r);
 
+    bool isInherited() const; // inherited from base class
+    void setInherited(bool i);
+
     bool isAccessModifier() const
     {
         return (modifiers() & AccessModifierMask) != 0;
@@ -212,8 +239,6 @@ public:
 
     bool isRemoveModifier() const { return isRemoved(); }
 
-
-
     bool isCodeInjection() const
     {
         return modifiers().testFlag(CodeInjection);
@@ -253,6 +278,16 @@ public:
 #endif
 
 private:
+    bool equals(const FunctionModification &rhs) const noexcept;
+    size_t hash(size_t seed) const noexcept;
+
+    Q_DECLARE_EQUALITY_COMPARABLE(FunctionModification)
+
+    friend bool comparesEqual(const FunctionModification &lhs, const FunctionModification &rhs) noexcept
+    {  return lhs.equals(rhs); }
+    friend size_t qHash(const FunctionModification &f, size_t seed = 0) noexcept
+    { return f.hash(seed); }
+
     QSharedDataPointer<FunctionModificationData> d;
 };
 
@@ -335,6 +370,9 @@ public:
     DocumentationEmphasis emphasis() const { return m_emphasis; }
     void setEmphasis(DocumentationEmphasis newEmphasis) { m_emphasis = newEmphasis; }
 
+    DocumentationTarget target() const { return m_target; }
+    void setTarget(DocumentationTarget newTarget) { m_target = newTarget; }
+
 private:
     QString m_code;
     QString m_xpath;
@@ -342,6 +380,7 @@ private:
     TypeSystem::DocModificationMode m_mode = TypeSystem::DocModificationXPathReplace;
     DocumentationFormat m_format = DocumentationFormat::Native;
     DocumentationEmphasis m_emphasis = DocumentationEmphasis::None;
+    DocumentationTarget m_target = DocumentationTarget::Documentation;
 };
 
 #endif // MODIFICATIONS_H
index 3b86c55d3b1a5c1c3d318e5a454a2909f8480516..aa31f352521fb5317ecac4c968c8597bca818693 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef MODIFICATIONS_TYPEDEFS_H
 #define MODIFICATIONS_TYPEDEFS_H
 
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
 
 #include <memory>
 
index f2e64c7e423c3062ed0e1cf207fc6d8f50eaf35c..9980fa441a26488bb47b4cdab81253e58fdd0d20 100644 (file)
@@ -5,8 +5,8 @@
 #include "messages.h"
 #include "exception.h"
 
-#include <QtCore/QDir>
-#include <QtCore/QTextStream>
+#include <QtCore/qdir.h>
+#include <QtCore/qtextstream.h>
 
 using namespace Qt::StringLiterals;
 
@@ -152,20 +152,16 @@ void OptionsParser::process(Options *o)
 
 bool OptionsParserList::handleBoolOption(const QString &key, OptionSource source)
 {
-    for (const auto &p : std::as_const(m_parsers)) {
-        if (p->handleBoolOption(key, source))
-            return true;
-    }
-    return false;
+    auto pred = [&key, source](const OptionsParserPtr &p) { return p->handleBoolOption(key, source); };
+    return std::any_of(m_parsers.cbegin(), m_parsers.cend(), pred);
 }
 
 bool OptionsParserList::handleOption(const QString &key, const QString &value, OptionSource source)
 {
-    for (const auto &p : std::as_const(m_parsers)) {
-        if (p->handleOption(key, value, source))
-            return true;
-    }
-    return false;
+    auto pred = [&key, &value, source](const OptionsParserPtr &p) {
+        return p->handleOption(key, value, source);
+    };
+    return std::any_of(m_parsers.cbegin(), m_parsers.cend(), pred);
 }
 
 static void processOption(const QString &o, OptionSource source,
index d5557dc15700feac205b1ad5d74209bfb6ad953e..122887bb62bccb8cfd5d961ef6293ea7b51cf153 100644 (file)
@@ -4,8 +4,8 @@
 #ifndef OPTIONSPARSER_H
 #define OPTIONSPARSER_H
 
-#include <QtCore/QString>
-#include <QtCore/QStringList>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
 
 #include <memory>
 
index 32cefdc6fe0315fd318f8c29334f5ef7542a75f7..a5994bd7504241e2544d4c378196c0842bb5490b 100644 (file)
@@ -8,9 +8,9 @@
 #include <sourcelocation.h>
 #include <debughelpers_p.h>
 
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QRegularExpression>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qregularexpression.h>
 
 #include <algorithm>
 
@@ -503,7 +503,7 @@ void _ScopeModelItem::addEnum(const EnumModelItem &item)
     // A forward declaration of an enum ("enum class Foo;") is undistinguishable
     // from an enum without values ("enum class QCborTag {}"), so, add all
     // enums and replace existing ones without values by ones with values.
-    const int index = indexOfEnum(item->name());
+    const auto index = indexOfEnum(item->name());
     if (index >= 0) {
         if (item->hasValues() && !m_enums.at(index)->hasValues())
             m_enums[index] = item;
@@ -1100,6 +1100,8 @@ CodeModel::FunctionType _FunctionModelItem::_determineTypeHelper() const
     case CodeModel::Constructor:
     case CodeModel::CopyConstructor:
     case CodeModel::MoveConstructor:
+    case CodeModel::AssignmentOperator:
+    case CodeModel::MoveAssignmentOperator:
     case CodeModel::Destructor:
     case CodeModel::Signal:
     case CodeModel::Slot:
@@ -1113,6 +1115,24 @@ CodeModel::FunctionType _FunctionModelItem::_determineTypeHelper() const
         return m_functionType;
 
     auto newType = newTypeOpt.value();
+    // If clang did not pre-detect AssignmentOperator for some operator=(),
+    // it is an assignment from another type which we are not interested in.
+    if (newType == CodeModel::AssignmentOperator) {
+#ifndef CLANG_HAS_ASSIGNMENT_OPERATOR_CHECK
+        // For clang 14 (Yocto), add a manual check.
+        if (m_arguments.size() == 1 && !type().isVoid()
+            && type().qualifiedName() == m_arguments.constFirst()->type().qualifiedName()) {
+            switch (m_arguments.constFirst()->type().referenceType()) {
+            case NoReference:
+            case LValueReference:
+                return CodeModel::AssignmentOperator;
+            case RValueReference:
+                return CodeModel::MoveAssignmentOperator;
+            }
+        }
+#endif // !CLANG_HAS_ASSIGNMENT_OPERATOR_CHECK
+        return CodeModel::OtherAssignmentOperator;
+    }
     // It's some sort of dereference operator?!
     if (m_arguments.isEmpty()) {
         switch (newType) {
index 8b3831b30620c5cf097b65f882825db7be2b7ccc..fb46fab82f4f1d85b2ae10d6dfc7a999b7160835 100644 (file)
 #include "enumvalue.h"
 #include "typeinfo.h"
 
-#include <QtCore/QHash>
-#include <QtCore/QSet>
-#include <QtCore/QString>
-#include <QtCore/QStringList>
-#include <QtCore/QList>
-#include <QtCore/QWeakPointer>
+#include <QtCore/qhash.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qsharedpointer.h>
 
 #include <optional>
 #include <utility>
@@ -43,6 +42,8 @@ public:
         Signal,
         Slot,
         AssignmentOperator,
+        MoveAssignmentOperator,
+        OtherAssignmentOperator, // Assign from some other type
         CallOperator,
         ConversionOperator,
         DereferenceOperator, // Iterator's operator *
@@ -66,7 +67,6 @@ public:
     };
     Q_ENUM(ClassType)
 
-public:
     CodeModel();
     virtual ~CodeModel();
 
@@ -115,7 +115,6 @@ public:
         Kind_Variable = 10 << FirstKind | Kind_Member
     };
 
-public:
     virtual ~_CodeModelItem();
 
     int kind() const;
index f0a25c9db554ba3b72086f6b27f61caee754d06e..d29f965e07ab2742086487e1f41e0f46cf877c86 100644 (file)
@@ -6,7 +6,7 @@
 #ifndef CODEMODEL_FWD_H
 #define CODEMODEL_FWD_H
 
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
 
 #include <memory>
 
index 3749e16a8b31b9868281be8cd4f65e95afac2e91..fcaad3c9f20662154003d7eb7bec6a408a980afd 100644 (file)
@@ -3,9 +3,9 @@
 
 #include "enumvalue.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QString>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qtextstream.h>
 
 using namespace Qt::StringLiterals;
 
index 3c600fdde23e1e623461c8201fe46a2b52867934..bad2c0e58e9fb21847fa169de16e3d2829f65964 100644 (file)
@@ -6,7 +6,7 @@
 
 #include <QtCore/qtypes.h>
 #include <QtCore/qtclasshelpermacros.h>
-#include <QtCore/QtCompare>
+#include <QtCore/qcompare.h>
 
 QT_FORWARD_DECLARE_CLASS(QDebug)
 QT_FORWARD_DECLARE_CLASS(QString)
index 06eaf9ec354668c6834f63e218d20c3320ba0400..93627e6d57783663c164a4a10e6878e778d9a97b 100644 (file)
@@ -11,9 +11,9 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QStack>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qtextstream.h>
 
 #include <iostream>
 
@@ -110,6 +110,28 @@ QStringList TypeInfo::qualifiedName() const
     return d->m_qualifiedName;
 }
 
+QString TypeInfo::qualifiedNameString() const
+{
+    return d->m_qualifiedName.join("::"_L1);
+}
+
+QString TypeInfo::qualifiedInstantationName() const
+{
+    QString result = qualifiedNameString();
+    if (const auto instantiationCount = d->m_instantiations.size()) {
+        result += u'<';
+        for (qsizetype i = 0; i < instantiationCount; ++i) {
+            if (i)
+                result += ", "_L1;
+            result += d->m_instantiations.at(i).toString();
+        }
+        if (result.endsWith(u'>'))
+            result += u' ';
+        result += u'>';
+    }
+    return result;
+}
+
 void TypeInfo::setQualifiedName(const QStringList &qualified_name)
 {
     if (d->m_qualifiedName != qualified_name)
@@ -300,7 +322,7 @@ TypeInfo TypeInfo::resolveType(const CodeModelItem &__item, TypeInfo const &__ty
         // typedef struct xcb_connection_t xcb_connection_t;
         if (nextItem.get() ==__item.get()) {
             std::cerr << "** WARNING Bailing out recursion of " << __FUNCTION__
-                << "() on " << qPrintable(__type.qualifiedName().join(u"::"_s))
+                << "() on " << qPrintable(__type.qualifiedNameString())
                 << '\n';
             return otherType;
         }
@@ -385,19 +407,7 @@ QString TypeInfo::toString() const
     if (isVolatile())
         tmp += u"volatile "_s;
 
-    tmp += d->m_qualifiedName.join(u"::"_s);
-
-    if (const auto instantiationCount = d->m_instantiations.size()) {
-        tmp += u'<';
-        for (qsizetype i = 0; i < instantiationCount; ++i) {
-            if (i)
-                tmp += u", "_s;
-            tmp += d->m_instantiations.at(i).toString();
-        }
-        if (tmp.endsWith(u'>'))
-            tmp += u' ';
-        tmp += u'>';
-    }
+    tmp += qualifiedInstantationName();
 
     for (Indirection i : d->m_indirections)
         tmp.append(indirectionKeyword(i));
index ef57d736d75d9d17bd8be06028a4328dd77628b9..6f75b57375afbf05c439fc86799d253f107f1dc2 100644 (file)
@@ -8,10 +8,10 @@
 #include "codemodel_enums.h"
 #include "codemodel_fwd.h"
 
-#include <QtCore/QString>
-#include <QtCore/QSharedDataPointer>
-#include <QtCore/QtCompare>
-#include <QtCore/QStringList>
+#include <QtCore/qstring.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qcompare.h>
+#include <QtCore/qstringlist.h>
 
 #include <utility>
 
@@ -39,6 +39,11 @@ public:
 
     QStringList qualifiedName() const;
     void setQualifiedName(const QStringList &qualified_name);
+    // Returns "std::list"
+    QString qualifiedNameString() const;
+    // Returns qualifiedName() with instantions ("std::list<int>")
+    QString qualifiedInstantationName() const;
+
     void addName(const QString &);
 
     bool isVoid() const;
index 9a9614f0d13ba0b1c60f460dba8eb4240eb4fe8c..22727282e5facfce8de27f7a1a013205df493254 100644 (file)
@@ -114,7 +114,7 @@ static QString cppMultiMapToPyDict(bool isQMultiMap)
         Py_ssize_t idx = 0;
         for (; it != upper; ++it, ++idx) {
             const auto &cppItem = it.value();
-            PyList_SET_ITEM(pyValues, idx, %CONVERTTOPYTHON[%INTYPE_1](cppItem));
+            PyList_SetItem(pyValues, idx, %CONVERTTOPYTHON[%INTYPE_1](cppItem));
         }
         PyDict_SetItem(%out, pyKey, pyValues);
         Py_DECREF(pyKey);
@@ -138,7 +138,7 @@ static QString cppMultiHashToPyDict(bool isQMultiHash)
         Py_ssize_t idx = 0;
         for (; it != range.second; ++it, ++idx) {
             const auto &cppItem = it.value();
-            PyList_SET_ITEM(pyValues, idx, %CONVERTTOPYTHON[%INTYPE_1](cppItem));
+            PyList_SetItem(pyValues, idx, %CONVERTTOPYTHON[%INTYPE_1](cppItem));
         }
         PyDict_SetItem(%out, pyKey, pyValues);
         Py_DECREF(pyKey);
@@ -195,7 +195,7 @@ return %out;
 Py_ssize_t idx = 0;
 for (auto it = std::cbegin(%in), end = std::cend(%in); it != end; ++it, ++idx) {
     const auto &cppItem = *it;
-    PyList_SET_ITEM(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
+    PyList_SetItem(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
 }
 return %out;)"_s},
 
@@ -254,8 +254,8 @@ QByteArray containerTypeSystemSnippet(const char *name, const char *type,
                                       const char *targetToNativeType,
                                       const char *targetToNative)
 {
-    QByteArray result = QByteArrayLiteral("<container-type name=\"")
-            + name + QByteArrayLiteral("\" type=\"") + type + R"(">
+    QByteArray result = "<container-type name=\""_ba
+            + name + "\" type=\""_ba + type + R"(">
     <include file-name=")" + include + R"(" location="global"/>
     <conversion-rule>
         <native-to-target>
@@ -263,16 +263,16 @@ QByteArray containerTypeSystemSnippet(const char *name, const char *type,
         </native-to-target>
 )";
     if (targetToNativeType != nullptr) {
-        result += QByteArrayLiteral(R"(        <target-to-native>
-            <add-conversion type=")") + targetToNativeType
-                + QByteArrayLiteral(R"(">
-                <insert-template name=")") + targetToNative + QByteArrayLiteral(R"("/>
+        result += R"(        <target-to-native>
+            <add-conversion type=")"_ba + targetToNativeType
+                + R"(">
+                <insert-template name=")"_ba + targetToNative + R"("/>
             </add-conversion>
         </target-to-native>
-)");
+)"_ba;
         }
-result += QByteArrayLiteral(R"(    </conversion-rule>
+result += R"(    </conversion-rule>
 </container-type>
-)");
+)"_ba;
     return result;
 }
index 0cc2c7f32cdf29a8da8ecd2235dabbfd7d0ec584..8ec9958a1c0671d90d000ccc754dd54880db1ce8 100644 (file)
@@ -4,8 +4,8 @@
 #ifndef PREDEFINED_TEMPLATES_H
 #define PREDEFINED_TEMPLATES_H
 
-#include <QtCore/QList>
-#include <QtCore/QString>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
 
 struct PredefinedTemplate
 {
index 6faaf7a61ce2b65241fef8c000f13fbda859676e..94010217f0252d7b9099db178a78ec2bb278fa96 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef PRIMITIVETYPEENTRY_H
 #define PRIMITIVETYPEENTRY_H
 
-#include "typesystem.h"
+#include "cpptypeentry.h"
 #include "customconversion_typedefs.h"
 
 class PrimitiveTypeEntryPrivate;
@@ -12,16 +12,12 @@ class PrimitiveTypeEntryPrivate;
 /// A PrimitiveTypeEntry is user-defined type with conversion rules, a C++
 /// primitive type for which a PrimitiveTypeConverter exists in libshiboken
 /// or a typedef to a C++ primitive type as determined by AbstractMetaBuilder.
-class PrimitiveTypeEntry : public TypeEntry
+class PrimitiveTypeEntry : public CppTypeEntry
 {
 public:
     explicit PrimitiveTypeEntry(const QString &entryName, const QVersionNumber &vr,
                                 const TypeEntryCPtr &parent);
 
-    QString defaultConstructor() const;
-    void setDefaultConstructor(const QString& defaultConstructor);
-    bool hasDefaultConstructor() const;
-
     /**
      *   The PrimitiveTypeEntry pointed by this type entry if it
      *   represents a typedef).
@@ -34,7 +30,7 @@ public:
      *   Defines type referenced by this entry.
      *   \param referencedTypeEntry type referenced by this entry
      */
-    void setReferencedTypeEntry(PrimitiveTypeEntryPtr referencedTypeEntry);
+    void setReferencedTypeEntry(const PrimitiveTypeEntryPtr &referencedTypeEntry);
 
     /// Returns whether this entry references another entry.
     bool referencesType() const;
index 32b756fadb3c118d0c0ad90b61f2bab4775c64bc..158d9f6276c5335d8e318325ed74688825daf186 100644 (file)
 
 #include "qtcompat.h"
 
-#include <QtCore/QHash>
+#include <QtCore/qhash.h>
 
 #ifndef QT_NO_DEBUG_STREAM
-#  include <QtCore/QDebug>
+#  include <QtCore/qdebug.h>
 #endif
 
 #include <algorithm>
@@ -220,7 +220,7 @@ TypeSystemProperty QPropertySpec::typeSystemPropertyFromQ_Property(const QString
         return result;
     }
 
-    const auto firstToken = qsizetype(it - propertyTokens.cbegin());
+    const auto firstToken = it - propertyTokens.cbegin();
     if (firstToken < 2) {
         *errorMessage = u"Insufficient number of tokens in property specification"_s;
         return result;
index 9e2e0f3d4131d66e681bfe0c10d343d7fd0e53ff..cbd2ba655e318220f2ad400230a76ca146ec227b 100644 (file)
@@ -9,8 +9,8 @@ class AbstractMetaType;
 #include "abstractmetalang_typedefs.h"
 #include "typesystem_typedefs.h"
 
-#include <QtCore/QStringList>
-#include <QtCore/QSharedDataPointer>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qshareddata.h>
 
 #include <optional>
 
index 64d44378b302e37010663cb640bec45ed0a5fcb4..b852b2dcb40ff875f62895830c4af1ae71cbc7ce 100644 (file)
@@ -4,7 +4,7 @@
 #include "pymethoddefentry.h"
 #include "textstream.h"
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 TextStream &operator<<(TextStream &str, const castToPyCFunction &c)
 {
index a8694eb3016f52fdb1590d4858e692ef2fd2312a..029113dd9c2f5de6f823fd4546d4fdbb8d61f4ca 100644 (file)
@@ -4,8 +4,8 @@
 #ifndef PYMETHODDEFENTRY_H
 #define PYMETHODDEFENTRY_H
 
-#include <QtCore/QByteArrayList>
-#include <QtCore/QString>
+#include <QtCore/qbytearraylist.h>
+#include <QtCore/qstring.h>
 
 QT_FORWARD_DECLARE_CLASS(QDebug)
 
index 2e0fbda97e52dc0e2bf077cebab42c2e5b226089..0967e52f4aeacd8d37a8ca7f89cbaff4f952f538 100644 (file)
@@ -23,7 +23,7 @@ public:
 #endif
 
 protected:
-    explicit PythonTypeEntry(TypeEntryPrivate *d);
+    explicit PythonTypeEntry(CustomTypeEntryPrivate *d);
 };
 
 #endif // PYTHONTYPEENTRY_H
index 3837dcfd2bc62a0856d442de932708ecb71e05a2..884c44df01378d1beea1bb1f7c7c5d9f5b391a6a 100644 (file)
@@ -10,7 +10,7 @@
 
 // QTBUG-98434, provide literals of Qt 6.4 for compatibility.
 
-#  include <QtCore/QString>
+#  include <QtCore/qstring.h>
 
 # define QLatin1StringView QLatin1String
 
index daafe685add12561f431bf0a3bedefa260cb9a00..c7361b87a1416d683aad2ab3a207be739e8a9411 100644 (file)
 
 #include "qtcompat.h"
 
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QHash>
-#include <QtCore/QUrl>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qurl.h>
 
 #include <algorithm>
 #include <iterator>
@@ -469,6 +469,10 @@ bool QtDocParser::extractEnumDocumentation(const ClassDocumentation &classDocume
             doc.insert(firstPara + 6, note);
         }
     }
+    // Fully qualify enums: "<term>QFrame::NoFrame" -> "<term>QFrame::FrameShape::NoFrame"
+    const QString classQualifier = "<term>"_L1 + classDocumentation.name + "::"_L1;
+    doc.replace(classQualifier, classQualifier + meta_enum.name() + "::"_L1);
+    doc.replace("::None</term>"_L1, "::None\\_</term>"_L1);
     Documentation enumDoc(doc, {}, sourceFileName);
     meta_enum.setDocumentation(enumDoc);
     return true;
index cf2fa163b7e599b01601543a76c8c7456105845f..95ea2ce447593f38e1414708e83acd87fc3a9b91 100644 (file)
@@ -3,28 +3,31 @@
 
 #include "reporthandler.h"
 #include "typedatabase.h"
+#include "messages.h"
 
-#include "qtcompat.h"
+#include <QtCore/qelapsedtimer.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qoperatingsystemversion.h>
+#include <QtCore/qset.h>
 
-#include <QtCore/QElapsedTimer>
-#include <QtCore/QSet>
 #include <cstring>
 #include <cstdarg>
 #include <cstdio>
 
 using namespace Qt::StringLiterals;
 
-#if defined(_WINDOWS) || defined(NOCOLOR)
-    #define COLOR_END ""
-    #define COLOR_WHITE ""
-    #define COLOR_YELLOW ""
-    #define COLOR_GREEN ""
-#else
-    #define COLOR_END "\033[0m"
-    #define COLOR_WHITE "\033[1;37m"
-    #define COLOR_YELLOW "\033[1;33m"
-    #define COLOR_GREEN "\033[0;32m"
+static const auto COLOR_END = "\033[0m"_ba;
+static const auto COLOR_YELLOW = "\033[1;33m"_ba;
+static const auto COLOR_GREEN = "\033[0;32m"_ba;
+
+static constexpr bool useTerminalColors()
+{
+    return QOperatingSystemVersion::currentType() != QOperatingSystemVersion::Windows
+#ifdef NOCOLOR
+        && false
 #endif
+        ;
+}
 
 static bool m_silent = false;
 static int m_warningCount = 0;
@@ -174,10 +177,13 @@ void ReportHandler::endProgress()
     std::fputs(m_progressMessage.constData(), stdout);
     if (m_progressMessage.size() < 60)
         indentStdout(60 - m_progressMessage.size());
-    const char *endMessage = m_step_warning == 0
-        ?  "[" COLOR_GREEN "OK" COLOR_END "]\n"
-        : "[" COLOR_YELLOW "WARNING" COLOR_END "]\n";
-    std::fputs(endMessage, stdout);
+    static const QByteArray ok = '['
+        + (useTerminalColors() ? COLOR_GREEN + "OK"_ba + COLOR_END : "OK"_ba)
+        + "]\n"_ba;
+    static const QByteArray warning = '['
+        + (useTerminalColors() ? COLOR_YELLOW + "WARNING"_ba + COLOR_END : "WARNING"_ba)
+        + "]\n"_ba;
+    std::fputs(m_step_warning == 0 ? ok.constData() : warning.constData(), stdout);
     std::fflush(stdout);
     m_progressMessage.clear();
     m_step_warning = 0;
@@ -192,3 +198,25 @@ QByteArray ReportHandler::doneMessage()
         result += " (" + QByteArray::number(m_suppressedCount) + " known issues)";
     return  result;
 }
+
+static QStringList generalMessages;
+
+void ReportHandler::addGeneralMessage(const QString &message)
+{
+    generalMessages.append(message);
+}
+
+void ReportHandler::writeGeneralLogFile(const QString &directory)
+{
+    if (generalMessages.isEmpty())
+        return;
+    QFile file(directory + "/mjb_shiboken.log"_L1);
+    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
+        qWarning(lcShiboken, "%s", qPrintable(msgCannotOpenForWriting(file)));
+        return;
+    }
+    for (const auto &m : std::as_const(generalMessages)) {
+        file.write(m.toUtf8());
+        file.putChar('\n');
+    }
+}
index b79adfa738f4f43dbead77e194442a3ca4236a20..9444901903d226e6765238780afc1ae489a3ed48 100644 (file)
@@ -4,8 +4,8 @@
 #ifndef REPORTHANDLER_H
 #define REPORTHANDLER_H
 
-#include <QtCore/QLoggingCategory>
-#include <QtCore/QString>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qstring.h>
 
 Q_DECLARE_LOGGING_CATEGORY(lcShiboken)
 Q_DECLARE_LOGGING_CATEGORY(lcShibokenDoc)
@@ -39,6 +39,9 @@ public:
 
     static QByteArray doneMessage();
 
+    static void addGeneralMessage(const QString &message);
+    static void writeGeneralLogFile(const QString &directory);
+
 private:
     static void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
 };
index 7b712fe35e5234906f20af3dd28660acbbb583e5..d704210f796231eb218097d928cd47dc598354c5 100644 (file)
@@ -41,9 +41,13 @@ public:
 
     TypeEntry *clone() const override;
 
+    bool matchesInstantiation(const TypeEntryCPtr &e) const;
+
     const Instantiations &instantiations() const;
     void setInstantiations(const Instantiations &i);
-    bool matchesInstantiation(const TypeEntryCPtr &e) const;
+
+    void setExcludedInstantiations(const TypeEntryCList &ex);
+    const TypeEntryCList &excludedInstantiations() const;
 
     QString getTargetName(const AbstractMetaType &metaType) const;
 
index 003f201acca4a16d80444ff626e42eb71e85e228..f13ff3422484f82c80e20b66e82bf63ded58394c 100644 (file)
@@ -2,8 +2,8 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "sourcelocation.h"
-#include <QtCore/QDir>
-#include <QtCore/QDebug>
+#include <QtCore/qdir.h>
+#include <QtCore/qdebug.h>
 
 SourceLocation::SourceLocation() = default;
 
index 0b188dca3f91757e3d8a9f1e08803cc2e4a3b5df..ccc24bcfe3811f08367267af9310c684694d8200 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef SOURCE_LOCATION_H
 #define SOURCE_LOCATION_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 QT_FORWARD_DECLARE_CLASS(QDebug)
 QT_FORWARD_DECLARE_CLASS(QTextStream)
index 4b5da0c3ac0371fdff7508394d0439704eae440f..f1349b0fed9a88552e60bb1484dfe57a9cd2e099 100644 (file)
@@ -12,7 +12,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
@@ -539,6 +539,89 @@ void TestAbstractMetaClass::testObjectTypesMustNotHaveCopyConstructors()
     QCOMPARE(ctors.constFirst()->minimalSignature(), u"A()");
 }
 
+void TestAbstractMetaClass::testValueConstructors_data()
+{
+    QTest::addColumn<QByteArray>("cppCode");
+    QTest::addColumn<bool>("expectedDefaultConstructible");
+    QTest::addColumn<bool>("expectedCopyConstructible");
+
+    QByteArray cppCode = R"(class A {};)";
+
+    QTest::newRow("implicit copy/default") << cppCode << true << true;
+
+    cppCode = R"(class A {
+private:
+    A();
+    A(const A &);
+public:
+};)";
+
+    QTest::newRow("old style - private copy/default") << cppCode << false << false;
+
+    cppCode = R"(class A {
+public:
+    A() = delete;
+    A(const A &) = delete;
+};)";
+
+    QTest::newRow("deleted copy/default") << cppCode << false << false;
+
+    cppCode = R"(class A {
+public:
+    A() = default;
+    A &operator=(const A &) = delete;
+};)";
+
+    QTest::newRow("default/deleted assignment") << cppCode << true << false;
+
+    cppCode = R"(class A {
+public:
+    A() = default;
+    A &operator=(A &&) = delete;
+};)";
+
+    QTest::newRow("default/deleted move assignment") << cppCode << true << false;
+
+    // An assignment from another type should not impact copy copy constructibility
+    cppCode = R"(class A {
+public:
+    A() = default;
+    A &operator=(int v);
+};)";
+
+    QTest::newRow("assignment from other type") << cppCode << true << true;
+
+    // Another constructor present, move deleted -> non default copy/default constructible
+    cppCode = R"(class A {
+public:
+    A(int x);
+    A(A &&) = delete;
+};)";
+
+    QTest::newRow("param-constructor, deleted move") << cppCode << false << false;
+}
+
+void TestAbstractMetaClass::testValueConstructors()
+{
+    QFETCH(QByteArray, cppCode);
+    QFETCH(bool, expectedDefaultConstructible);
+    QFETCH(bool, expectedCopyConstructible);
+
+    const char xmlCode[] = R"(<typesystem package='Foo'>
+    <value-type name='A'/>
+</typesystem>
+)";
+
+    QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode.constData(), xmlCode));
+    QVERIFY(builder);
+    AbstractMetaClassList classes = builder->classes();
+    QCOMPARE(classes.size(), 1);
+    const auto classA = AbstractMetaClass::findClass(classes, "A");
+    QVERIFY(classA);
+    QCOMPARE(classA->isDefaultConstructible(), expectedDefaultConstructible);
+    QCOMPARE(classA->isCopyConstructible(), expectedCopyConstructible);
+}
+
 void TestAbstractMetaClass::testIsPolymorphic()
 {
     const char cppCode[] = "\
index a6bd2bf065004a5f39e0aaf063b68a8e09976413..69e391037b93cb2554e53c2f8a71214d9cf5edd0 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTABSTRACTMETACLASS_H
 #define TESTABSTRACTMETACLASS_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class AbstractMetaBuilder;
 
@@ -25,6 +25,8 @@ private slots:
     void testClassInheritedDefaultConstructors();
     void testAbstractClassDefaultConstructors();
     void testObjectTypesMustNotHaveCopyConstructors();
+    void testValueConstructors_data();
+    void testValueConstructors();
     void testIsPolymorphic();
     void testClassTypedefedBaseClass();
     void testFreeOperators_data();
index 2c320c87434ee5f48dd3432cf5c1a76deafd0a1e..a6983f817be1c39558d2533909850a9b5a63612d 100644 (file)
@@ -13,7 +13,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index fdcf0c7872085864913c464d137b7466c08c5b9e..db2332efa569feb50912a199737fc65c7675cfdd 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTABSTRACTMETATYPE_H
 #define TESTABSTRACTMETATYPE_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestAbstractMetaType : public QObject
 {
index a891e1e28d92d998c92e6d93f8dd4cf461b87ddf..db58188c3c43cb0cc2107272ef660c63bdcf33f7 100644 (file)
@@ -15,7 +15,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 77339609fcae3cb91527b77dbf1293c5d2808c26..a3c97708fa3b0f4a1caeca1d01184dc368cafa73 100644 (file)
@@ -3,7 +3,8 @@
 
 #ifndef TESTADDFUNCTION_H
 #define TESTADDFUNCTION_H
-#include <QtCore/QObject>
+
+#include <QtCore/qobject.h>
 
 class TestAddFunction : public QObject
 {
index 6e1820bedf446aac2345f1842a38c8cc50a76d27..f1bc3860a10775684e9c5e48ef5a2e5327f42cf9 100644 (file)
@@ -13,7 +13,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 75ef0f792642581a1cfe4e1fe9fc4aa3d3318e12..429e84ba15908dca2f2fb8c3845bccd3233e2beb 100644 (file)
@@ -3,7 +3,8 @@
 
 #ifndef TESTARRAYARGUMENT_H
 #define TESTARRAYARGUMENT_H
-#include <QtCore/QObject>
+
+#include <QtCore/qobject.h>
 
 class TestArrayArgument : public QObject
 {
index 4829e6c33498ba0ffd13d7b6fc46daaa86bbf6d9..49b40cb0b8adba035ab426e1f1fdbe6f59bbb8eb 100644 (file)
 
 #include <qtcompat.h>
 
-#include <QtCore/QDir>
-#include <QtCore/QFileInfo>
-#include <QtTest/QTest>
+#include <QtCore/qdir.h>
+#include <QtCore/qfileinfo.h>
+
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index a164ea36ed9df5f5aa87e4a4388d186f530875ee..0483fbd01e9fe4e12061fd4689da61bf616802be 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTCODEINJECTIONS_H
 #define TESTCODEINJECTIONS_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class AbstractMetaBuilder;
 
index e185e91cb39ab58842e3e89432a58c27849b721d..1e8d476c2c4e2030df616566153dc795c82a4bc3 100644 (file)
@@ -2,13 +2,15 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "testcontainer.h"
-#include <QtTest/QTest>
 #include "testutil.h"
+
 #include <abstractmetalang.h>
 #include <abstractmetatype.h>
 #include <complextypeentry.h>
 #include <containertypeentry.h>
 
+#include <QtTest/qtest.h>
+
 void TestContainer::testContainerType()
 {
     const char cppCode[] = "\
index 3fd23c3f0cbd15b9b5fd4b0d4576360310b8b673..d09422c70c8627d71e25566cfe5c9bed84c643ba 100644 (file)
@@ -3,7 +3,7 @@
 
 #ifndef TESTCONTAINER_H
 #define TESTCONTAINER_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestContainer : public QObject
 {
index 8f2b277af308508e6c162921eb5359fd16df128b..8912be08e8639f4c17c8baa6aaac33dfb6d674f3 100644 (file)
@@ -10,7 +10,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 68288d240ba7e093e5d932d455c5ce433034d7d0..c0ce2d1ab07f61ab5547c47c8498f47750ac1616 100644 (file)
@@ -3,7 +3,8 @@
 
 #ifndef TESTCONVERSIONOPERATOR_H
 #define TESTCONVERSIONOPERATOR_H
-#include <QtCore/QObject>
+
+#include <QtCore/qobject.h>
 
 class TestConversionOperator : public QObject
 {
index b5efd92a641b135413d5253deda3ae51f02fd212..6b9146a436ec343168628fc93e0bf8b5d2addab7 100644 (file)
 
 #include <qtcompat.h>
 
-#include <QtCore/QFile>
-#include <QtCore/QTemporaryFile>
-#include <QtTest/QTest>
+#include <QtCore/qfile.h>
+#include <QtCore/qtemporaryfile.h>
+
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 64d496cc36b048b580e9dfa2cd5a880cc1c662dc..21efa3c97db5f5db7b3789830148d47b1da279db 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTCONVERSIONRULE_H
 #define TESTCONVERSIONRULE_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestConversionRuleTag : public QObject
 {
index c3a3ebef0846eebe58a9d2ec296e3a0bd12d3499..2731aafbbbdb0dd036b7d8a334bea925fc1644a0 100644 (file)
@@ -3,11 +3,13 @@
 
 #include "testctorinformation.h"
 #include "abstractmetabuilder.h"
-#include <QtTest/QTest>
 #include "testutil.h"
+
 #include <abstractmetalang.h>
 #include <typesystem.h>
 
+#include <QtTest/qtest.h>
+
 void TestCtorInformation::testCtorIsPrivate()
 {
     const char cppCode[] = "class Control { public: Control() {} };\n\
index 58f1648e4979ecb9021854b8fe436e6b917c5ac5..eddec439bebd703222c9558d3ef87a50a66eedb3 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTCTORINFORMATION_H
 #define TESTCTORINFORMATION_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class AbstractMetaBuilder;
 
index 16f50e69dc3a75617b036e70c31394420cad4d9f..d9fbb5b52f950eb85fd37ce4639a40a0c588ae49 100644 (file)
@@ -10,7 +10,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 98717bd215c1ce3202309e6818490b80422d96cb..95822dd7d589ec1d66713d87a0493b07e3002403 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTDROPTYPEENTRIES_H
 #define TESTDROPTYPEENTRIES_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestDropTypeEntries : public QObject
 {
index 649da174101e219e77bc7e2b0574f53d834439ab..15c1c996a5a21c7c041dc1352f4a010bbfd79fbe 100644 (file)
@@ -3,11 +3,12 @@
 
 #include "testdtorinformation.h"
 #include "abstractmetabuilder.h"
-#include <QtTest/QTest>
 #include "testutil.h"
 #include <abstractmetalang.h>
 #include <typesystem.h>
 
+#include <QtTest/qtest.h>
+
 void TestDtorInformation::testDtorIsPrivate()
 {
     const char cppCode[] = R"(class Control {
index 0f8cb59b3b337f45517ac2b51c240e3e68ad9d65..304d0bc798b0c6596f4226abc0c4e8ca6da289d5 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTDTORINFORMATION_H
 #define TESTDTORINFORMATION_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class AbstractMetaBuilder;
 
index c7c2b8b3bbae0fffe4ab7c834d0bd7326c9c6c3d..95eea18e5babd849f07a97f42c186e58b5d82c88 100644 (file)
@@ -14,7 +14,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 452755490304c4d466c49fcc8490976fe3bcd844..4ab1b566548ee2211a61d92b3d44ce7e08f9637b 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTENUM_H
 #define TESTENUM_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestEnum : public QObject
 {
index fcc409a42f6afc175455dd5388eedbf960e8b60d..a95b71507ff6140b8c6b9931310c7ac88f2aaf5e 100644 (file)
@@ -2,12 +2,14 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "testextrainclude.h"
-#include <QtTest/QTest>
 #include "testutil.h"
+
 #include <abstractmetalang.h>
 #include <complextypeentry.h>
 #include <typesystemtypeentry.h>
 
+#include <QtTest/qtest.h>
+
 void TestExtraInclude::testClassExtraInclude()
 {
     const char cppCode[] = "struct A {};\n";
index 6bcb579937b041d98006c152d3a9a68509ca489c..ae1a18686bdfc53667c436e862e5bc0ba06f373a 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTEXTRAINCLUDE_H
 #define TESTEXTRAINCLUDE_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestExtraInclude : public QObject
 {
index f8b6257c90b9773c5c551ff3931640c20a89584c..474928b198992aa0ed73467e919da49da3fd4b9e 100644 (file)
@@ -9,7 +9,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 7c60cb4e0a52b491a79b99ca61b4290a5180ea32..e9e447ae36f62f8b63c9abd4b5f7e88fe2adbb81 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTFUNCTIONTAG_H
 #define TESTFUNCTIONTAG_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestFunctionTag : public QObject
 {
index 899d00ad49dda31de78219a6c9f69b2f5a8fb02a..746cfc66e93be3f3b9aa35a5ae3be3b90cf95d55 100644 (file)
@@ -3,12 +3,14 @@
 
 #include "testimplicitconversions.h"
 #include "testutil.h"
+
 #include <abstractmetaargument.h>
 #include <abstractmetafunction.h>
 #include <abstractmetalang.h>
 #include <abstractmetatype.h>
 #include <complextypeentry.h>
-#include <QtTest/QTest>
+
+#include <QtTest/qtest.h>
 
 void TestImplicitConversions::testWithPrivateCtors()
 {
index e0678c5f575dddc305999dcaeda05231504c392c..4e17ca7580b64fc2a34a085215a4ee69d353d0da 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTIMPLICITCONVERSIONS_H
 #define TESTIMPLICITCONVERSIONS_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class AbstractMetaBuilder;
 
index 23cf0f9ea8d9e389357e8492a1b657f1f4f3409d..24fcee616dc5435e396154fc297ce3c0dd2775fe 100644 (file)
@@ -2,14 +2,16 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "testinserttemplate.h"
-#include <QtTest/QTest>
 #include "testutil.h"
+
 #include <abstractmetalang.h>
 #include <codesnip.h>
 #include <modifications.h>
 #include <complextypeentry.h>
 #include <typesystemtypeentry.h>
 
+#include <QtTest/qtest.h>
+
 void TestInsertTemplate::testInsertTemplateOnClassInjectCode()
 {
     const char cppCode[] = "struct A{};\n";
index f4f67abc0b19932a181ba8819376e9f42a1bdb5a..7822f7d88d0510f1e7d391515ce7956d7ba5fcba 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTINSERTTEMPLATE_H
 #define TESTINSERTTEMPLATE_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestInsertTemplate : public QObject
 {
index 3858ab0ce4e00763fe22ca8ed6b51afff154f7e4..c2fc3b2127cfba5e9a9972fce30bd75055f43219 100644 (file)
@@ -12,9 +12,9 @@
 
 #include <qtcompat.h>
 
-#include <QtCore/QCoreApplication>
-#include <QtCore/QTemporaryDir>
-#include <QtTest/QTest>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qtemporarydir.h>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index c1cc8f4800cb0d0e0ce90f51f55c418c0dab8008..c1a6f9bb995cca58196942cdafe83687cf957452 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTMODIFYDOCUMENTATION_H
 #define TESTMODIFYDOCUMENTATION_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestModifyDocumentation : public QObject
 {
index a7d40f70a2e0b0927cb2f69526796fbb4baa020d..d70463509dc334a2b522c05dfaf9359f2b4d7e91 100644 (file)
@@ -13,7 +13,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 8a4f5d8264537b2254bef853948a841dc9954070..615ed05ca213f6e739d860db8d857a140da32c64 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTABSTRACTMETACLASS_H
 #define TESTABSTRACTMETACLASS_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestModifyFunction : public QObject
 {
index 1cf4c8e0f3387c1dd62c57fe4b132713e8955463..f803722540c5070bbd7ad500d40601abde803532 100644 (file)
@@ -2,12 +2,14 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "testmultipleinheritance.h"
-#include <QtTest/QTest>
 #include "testutil.h"
+
 #include <abstractmetafunction.h>
 #include <abstractmetalang.h>
 #include <typesystem.h>
 
+#include <QtTest/qtest.h>
+
 void TestMultipleInheritance::testVirtualClass()
 {
     const char cppCode[] = "\
index ec9935305605e7c4db834c2f26ae8b3322ecbd6e..16eec0b8a9420195cd76c1f891e9caf7f194545f 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTMULTIPLEINHERITANCE_H
 #define TESTMULTIPLEINHERITANCE_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class AbstractMetaBuilder;
 
index 600c1888c3ab5ab5861ee54d380aff0e5ea8d678..36612895d84fee76308b713e602b0a05b997db49 100644 (file)
@@ -9,7 +9,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index af46bdea35ed25a2009830715cbb49bd8f3b2691..b89c374abf9b2d7d637f8f1ba78bbc8421617789 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTNAMESPACE_H
 #define TESTNAMESPACE_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 // The class is named 'NamespaceTest' to avoid clashes with Qt COIN using
 // '-qtnamespace TestNamespace'.
index 10ca1a0f6a1195b8da909dc5d362015d41cbf176..6cab06428d0d45ccf76bc8b5617ed80a380aa7bb 100644 (file)
@@ -13,7 +13,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 544ea05abe42b4109b93f633427a473da58496ee..86377c9b29071b11860d58236984b84784f84683 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTNESTEDTYPES_H
 #define TESTNESTEDTYPES_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestNestedTypes : public QObject
 {
index 132d8ed50b2c22f749c5627b6617798ce4c582aa..9d2afe9191a8503b7816ae6b2468bfffc598bcca 100644 (file)
@@ -2,14 +2,16 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "testnumericaltypedef.h"
-#include <QtTest/QTest>
 #include "testutil.h"
+
 #include <abstractmetaargument.h>
 #include <abstractmetafunction.h>
 #include <abstractmetalang.h>
 #include <abstractmetatype.h>
 #include <typesystem.h>
 
+#include <QtTest/qtest.h>
+
 void TestNumericalTypedef::testNumericalTypedef()
 {
     const char cppCode[] = "\
index 32f54983648130797b4234d60f8faf5f2a4be917..eab1a02337fc56385adf5683983d68076656ec08 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTNUMERICALTYPEDEF_H
 #define TESTNUMERICALTYPEDEF_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestNumericalTypedef : public QObject
 {
index 6c2ea12216d3ecccbc6dae4375ee8c9ac5afca66..b49714d2e6ed885c1cc572d136ed117cf92317fc 100644 (file)
@@ -8,7 +8,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 3a0e05138fdf0b97a74d9d40dcdd95b6b64a4903..e374fd70618e47ce4de6f4783558b277b61188e6 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTPRIMITIVETYPETAG_H
 #define TESTPRIMITIVETYPETAG_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestPrimitiveTypeTag : public QObject
 {
index ffc9e688cc86487ba5192f48bf9fc76178d0c2ae..0fbc41be654a0de110be326666bdcf7b4a813cd8 100644 (file)
@@ -9,7 +9,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 6093c6f7b13ea3e92e095acd5e643392e1f9c91b..8f823f9bf8b5231e082b7c436ec7f22f2813cf43 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTREFCOUNTTAG_H
 #define TESTREFCOUNTTAG_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestRefCountTag : public QObject
 {
index 9eff8149424560726db64283bb2f41c4fd95b2b3..090fd59360a8007eb96148137be69e2c365dfc11 100644 (file)
@@ -2,14 +2,16 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "testreferencetopointer.h"
-#include <QtTest/QTest>
 #include "testutil.h"
+
 #include <abstractmetaargument.h>
 #include <abstractmetafunction.h>
 #include <abstractmetalang.h>
 #include <abstractmetatype.h>
 #include <typesystem.h>
 
+#include <QtTest/qtest.h>
+
 void TestReferenceToPointer::testReferenceToPointerArgument()
 {
     const char cppCode[] = "\
index 2a7b348076bc9932f9b8dc3e5a9d41ef22d23bd1..930056226087b2d322bd0ba6ff5fbc357d54ab83 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTREFERENCETOPOINTER_H
 #define TESTREFERENCETOPOINTER_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestReferenceToPointer : public QObject
 {
index 71d9e0df4a2f3be1ada73000a6104a6cdf279a65..60ea3a2cbe9c43bc541892cfc3e6be68bbf9718c 100644 (file)
@@ -2,8 +2,8 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "testremovefield.h"
-#include <QtTest/QTest>
 #include "testutil.h"
+
 #include <abstractmetaargument.h>
 #include <abstractmetafield.h>
 #include <abstractmetafunction.h>
@@ -11,6 +11,8 @@
 #include <abstractmetalang.h>
 #include <typesystem.h>
 
+#include <QtTest/qtest.h>
+
 using namespace Qt::StringLiterals;
 
 void TestRemoveField::testRemoveField()
index 05912d99e37885e6dd76d14e61f25335b583aa92..f5feba415351926533952f77e5c849b1ca99c7d8 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTREMOVEFIELD_H
 #define TESTREMOVEFIELD_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestRemoveField : public QObject
 {
index 87e318e9502a855e980655b80505b2df19b682e6..c0dfbcd79a7a9a5ac3c386a4311987930567b23c 100644 (file)
@@ -3,13 +3,15 @@
 
 #include "testremoveimplconv.h"
 #include "testutil.h"
-#include <QtTest/QTest>
+
 #include <abstractmetaargument.h>
 #include <abstractmetafunction.h>
 #include <abstractmetalang.h>
 #include <abstractmetatype.h>
 #include <complextypeentry.h>
 
+#include <QtTest/qtest.h>
+
 // When a constructor able to trigger implicity conversions is removed
 // it should not appear in the implicity conversion list.
 void TestRemoveImplConv::testRemoveImplConv()
index d11d30633e9b8a662b4d6a31eee43cb0e1d4394d..4efa10c7ff17a0ad1a43e5cbd14f3d8720f75e5e 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTREMOVEIMPLCONV_H
 #define TESTREMOVEIMPLCONV_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestRemoveImplConv : public QObject
 {
index 8557f3a7e4e1b895a9d4830e8ea3f96684becb77..3a85622d81ff3d95aed0a53187cd52fc935ce922 100644 (file)
@@ -9,7 +9,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 6ec335e0cad69089e3eaa97f94d31773d21c4889..8e993d351ef432bbb45aab088f47fa1840e8ceff 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTREMOVEOPERATORMETHOD_H
 #define TESTREMOVEOPERATORMETHOD_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestRemoveOperatorMethod : public QObject
 {
index 67ebcc606a6f2b1666a160a0fa317762767204f1..916195687e1f490e94ac08767902f7a7530033f4 100644 (file)
@@ -15,7 +15,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index a07855eab19dd5337cef49e3e7df709ec356401a..268305afd5e5bf12296a0c9ba431036e297a26f3 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTRESOLVETYPE_H
 #define TESTRESOLVETYPE_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestResolveType : public QObject
 {
index 86584d18178eaece629d6b241e40f5c897448bdd..7afca1170ec9423bd95a39c8b045567239d5c08c 100644 (file)
@@ -2,14 +2,16 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "testreverseoperators.h"
-#include <QtTest/QTest>
 #include "testutil.h"
+
 #include <abstractmetaargument.h>
 #include <abstractmetafunction.h>
 #include <abstractmetalang.h>
 #include <typesystem.h>
 #include <clangparser/compilersupport.h>
 
+#include <QtTest/qtest.h>
+
 #include <algorithm>
 
 void TestReverseOperators::testReverseSum()
index fb8d97c97ee2a00ac56a2d3d45d5b9b4254def7a..154ad7b281d5e3f7e9d4dc27db1ff2d874604171 100644 (file)
@@ -3,7 +3,7 @@
 
 #ifndef TESTREVERSEOPERATORS_H
 #define TESTREVERSEOPERATORS_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestReverseOperators : public QObject
 {
index 43b1b273883fdab528b45ef70fd41faa5558c174..fc740c227130764a0d877f4b779db3c4f4baea71 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "testtemplates.h"
 #include "testutil.h"
+
 #include <abstractmetaargument.h>
 #include <abstractmetafield.h>
 #include <abstractmetafunction.h>
@@ -13,9 +14,9 @@
 
 #include <qtcompat.h>
 
-#include <QtCore/QTemporaryFile>
-#include <QtCore/QTextStream>
-#include <QtTest/QTest>
+#include <QtCore/qtemporaryfile.h>
+#include <QtCore/qtextstream.h>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 36800f72381633757a1aa9de47a99258b78c5e7d..b0796cddc080c846134d34fa56f6fd8092b6b8a6 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTTEMPLATES_H
 #define TESTTEMPLATES_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestTemplates : public QObject
 {
index ec1cc5e28638346df64e4bfaa90a41c11d40c244..1cfb961b7eb9df0bf68cd482ff48aed128c9e57a 100644 (file)
@@ -4,8 +4,8 @@
 #include "testtoposort.h"
 #include "graph.h"
 
-#include <QtTest/QTest>
-#include <QtCore/QDebug>
+#include <QtTest/qtest.h>
+#include <QtCore/qdebug.h>
 
 using IntGraph = Graph<int>;
 
index 4271d6a0e890ae7349696094ee52d9515821dcc3..6aae6d8ff6782159bc3c5165cf76be4210497d47 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTTOPOSORT_H
 #define TESTTOPOSORT_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestTopoSort : public QObject
 {
index 2ba9efee0a68cc1ee921826929de822112a5ab1b..a66b6c875093464fa03289eb3fcbc0babf32691e 100644 (file)
@@ -12,7 +12,7 @@
 
 #include <qtcompat.h>
 
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
 
 using namespace Qt::StringLiterals;
 
index 84af839d236547d36ee9a1778763e5a6495524b8..77de06acdbdfbc3c42a5b72ebb34652797141b77 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTTYPEREVISION_H
 #define TESTTYPEREVISION_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestTypeRevision : public QObject
 {
index dc4e3b2da7edb5c3c1c6878fb3729bbec7d5838e..8f79b4a7d08e22ef9ee560e5671ade5bb86afbcb 100644 (file)
@@ -3,13 +3,15 @@
 
 #ifndef TESTUTIL_H
 #define TESTUTIL_H
-#include <QtCore/QBuffer>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QTemporaryFile>
-#include "abstractmetabuilder.h"
-#include "reporthandler.h"
-#include "typedatabase.h"
+
+#include <abstractmetabuilder.h>
+#include <reporthandler.h>
+#include <typedatabase.h>
+
+#include <QtCore/qbuffer.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qtemporaryfile.h>
 
 #include <exception>
 #include <memory>
index 98e30eac26d69f25d7bea6490f3050cca71bb9f8..94ac5f2f699b70dd35f8c4262efea48da8159071 100644 (file)
@@ -2,11 +2,13 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "testvaluetypedefaultctortag.h"
-#include <QtTest/QTest>
 #include "testutil.h"
+
 #include <abstractmetalang.h>
 #include <complextypeentry.h>
 
+#include <QtTest/qtest.h>
+
 void TestValueTypeDefaultCtorTag::testValueTypeDefaultCtorTagArgument()
 {
     const char cppCode[] = "\n\
index 192c07c1da511b7afbb7d41d61345147d1c6c43d..566e775c03fd20752e8ae7614c3788d5d0a10bb4 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TESTVALUETYPEDEFAULTCTORTAG_H
 #define TESTVALUETYPEDEFAULTCTORTAG_H
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class TestValueTypeDefaultCtorTag : public QObject
 {
index a600181a56f697defec9091f046c5796b3b2153b..188b8aab657a1d9ba56576e1d72e9b7a6c3f5254 100644 (file)
@@ -2,13 +2,15 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "testvoidarg.h"
-#include <QtTest/QTest>
 #include "testutil.h"
+
 #include <abstractmetaargument.h>
 #include <abstractmetafunction.h>
 #include <abstractmetalang.h>
 #include <typesystem.h>
 
+#include <QtTest/qtest.h>
+
 void TestVoidArg::testVoidParsedFunction()
 {
     const char cppCode[] = "struct A { void a(void); };";
index 191b9cfb252072f47a43c705f709afeb10666683..d4e22122a4b0114ed4137e120d8f7601dcbe78b3 100644 (file)
@@ -3,7 +3,8 @@
 
 #ifndef TESTVOIDARG_H
 #define TESTVOIDARG_H
-#include <QtCore/QObject>
+
+#include <QtCore/qobject.h>
 
 class TestVoidArg : public QObject
 {
index 228f36405a5ab016b7cee6270d73ed9259dfb5b9..3cf6d0716105fba9a3a7700b875adb58472b6650 100644 (file)
@@ -4,8 +4,8 @@
 #ifndef TEXTSTREAM_H
 #define TEXTSTREAM_H
 
-#include <QtCore/QTextStream>
-#include <QtCore/QString>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qstring.h>
 
 /// A text stream based on QTextStream with built-in indent.
 class TextStream
@@ -89,8 +89,8 @@ public:
     TextStream &operator<<(qsizetype t) { putSizeType(t); return *this; }
 #endif
 
-    inline TextStream &operator<<(const QTextStreamManipulator &m) { m_str << m; return *this; }
-    inline TextStream &operator<<(ManipulatorFunc f) { f(*this); return *this; }
+    TextStream &operator<<(const QTextStreamManipulator &m) { m_str << m; return *this; }
+    TextStream &operator<<(ManipulatorFunc f) { f(*this); return *this; }
 
     void putRepetitiveChars(char c, int count);
 
index 8ad7ebbdf5394f5620dd180227894294c593a4c6..5cebd850a889af7a803b847c773f60229c303359 100644 (file)
 
 #include "qtcompat.h"
 
-#include <QtCore/QBuffer>
-#include <QtCore/QFile>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QList>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QVersionNumber>
-#include <QtCore/QXmlStreamReader>
+#include <QtCore/qbuffer.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qversionnumber.h>
+#include <QtCore/qxmlstream.h>
 #include "reporthandler.h"
 
 #include <algorithm>
@@ -261,7 +261,11 @@ struct TypeDatabasePrivate : public TypeDatabaseOptions
     TypeEntryPtr resolveTypeDefEntry(const TypedefEntryPtr &typedefEntry, QString *errorMessage);
     template <class String>
     bool isSuppressedWarningHelper(const String &s) const;
-    bool resolveSmartPointerInstantiations(const TypeDatabaseParserContextPtr &context);
+    bool resolveSmartPointerInstantiations(const TypeDatabaseParserContextPtr &context) const;
+    bool resolveSmartPointerInstantiations(const SmartPointerTypeEntryPtr &smartPointerEntry,
+                                           const QString &s) const;
+    bool resolveSmartPointerExclusions(const SmartPointerTypeEntryPtr &smartPointerEntry,
+                                       const QString &s) const;
     void formatDebug(QDebug &d) const;
     void formatBuiltinTypes(QDebug &d) const;
 
@@ -274,6 +278,7 @@ struct TypeDatabasePrivate : public TypeDatabaseOptions
 
     AddedFunctionList m_globalUserFunctions;
     FunctionModificationList m_functionMods;
+    OverloadRemovalRules m_overloadRemovalRules;
 
     QStringList m_requiredTargetImports;
 
@@ -854,7 +859,8 @@ void TypeDatabase::addTemplate(const TemplateEntryPtr &t)
 
 void TypeDatabase::addTemplate(const QString &name, const QString &code)
 {
-    auto te = std::make_shared<TemplateEntry>(name);
+    auto te = std::make_shared<TemplateEntry>();
+    te->setName(name);
     te->addCode(code);
     addTemplate(te);
 }
@@ -901,6 +907,16 @@ FunctionModificationList
     return lst;
 }
 
+const OverloadRemovalRules &TypeDatabase::overloadRemovalRules() const
+{
+    return d->m_overloadRemovalRules;
+}
+
+void TypeDatabase::addOverloadRemovalRule(const OverloadRemovalRule &r)
+{
+    d->m_overloadRemovalRules.append(r);
+}
+
 bool TypeDatabase::addSuppressedWarning(const QString &warning, bool generate,
                                         QString *errorMessage)
 {
@@ -1171,14 +1187,19 @@ bool TypeDatabasePrivate::parseFile(QIODevice *device, TypeDatabase *db, bool ge
 bool TypeDatabase::parseFile(const TypeDatabaseParserContextPtr &context,
                              QIODevice *device, bool generate)
 {
-    return d->parseFile(context, device, generate);
+    return TypeDatabasePrivate::parseFile(context, device, generate);
 }
 
 bool TypeDatabasePrivate::parseFile(const TypeDatabaseParserContextPtr &context,
                                     QIODevice *device, bool generate)
 {
     ConditionalStreamReader reader(device);
-    reader.setConditions(context->db->typesystemKeywords());
+    const auto typesystemKeywords = context->db->typesystemKeywords();
+    if (generate) {
+        ReportHandler::addGeneralMessage("Type System keywords: "_L1
+                                         + typesystemKeywords.join(", "_L1) + u'\n');
+    }
+    reader.setConditions(typesystemKeywords);
     TypeSystemParser handler(context, generate);
     const bool result = handler.parse(reader);
     if (!result) {
@@ -1210,6 +1231,8 @@ static QStringList splitTypeList(const QString &s)
                 lastPos = i + 1;
             }
             break;
+        default:
+            break;
         }
     }
     if (lastPos < size)
@@ -1217,38 +1240,76 @@ static QStringList splitTypeList(const QString &s)
     return result;
 }
 
-bool TypeDatabasePrivate::resolveSmartPointerInstantiations(const TypeDatabaseParserContextPtr &context)
+bool TypeDatabasePrivate::resolveSmartPointerInstantiations(
+    const SmartPointerTypeEntryPtr &smartPointerEntry, const QString &s) const
+{
+    const auto instantiationNames = splitTypeList(s);
+    SmartPointerTypeEntry::Instantiations instantiations;
+    instantiations.reserve(instantiationNames.size());
+    for (const auto &instantiation : instantiationNames) {
+        QString name;
+        QString type = instantiation;
+        const auto equalsPos = instantiation.indexOf(u'=');
+        if (equalsPos != -1) {
+            type.truncate(equalsPos);
+            name = instantiation.mid(equalsPos + 1);
+        }
+
+        const auto typeEntries = findCppTypes(type);
+        if (typeEntries.isEmpty()) {
+            const QString m = msgCannotFindTypeEntryForSmartPointer(type,
+                                                                    smartPointerEntry->name());
+            qCWarning(lcShiboken, "%s", qPrintable(m));
+            return false;
+        }
+        if (typeEntries.size() > 1) {
+            const QString m = msgAmbiguousTypesFound(type, typeEntries);
+            qCWarning(lcShiboken, "%s", qPrintable(m));
+            return false;
+        }
+        instantiations.append({name, typeEntries.constFirst()});
+    }
+    smartPointerEntry->setInstantiations(instantiations);
+    return true;
+}
+
+bool TypeDatabasePrivate::resolveSmartPointerExclusions(
+    const SmartPointerTypeEntryPtr &smartPointerEntry, const QString &s) const
+{
+    const auto excludedNames = splitTypeList(s);
+    TypeEntryCList excluded;
+
+    excluded.reserve(excludedNames.size());
+    for (const auto &excludedName : excludedNames) {
+        const auto typeEntries = findCppTypes(excludedName);
+        if (typeEntries.isEmpty()) {
+            const QString m = msgCannotFindTypeEntryForSmartPointer(excludedName,
+                                                                    smartPointerEntry->name());
+            qCWarning(lcShiboken, "%s", qPrintable(m));
+            return false;
+        }
+        if (typeEntries.size() > 1) {
+            const QString m = msgAmbiguousTypesFound(excludedName, typeEntries);
+            qCWarning(lcShiboken, "%s", qPrintable(m));
+            return false;
+        }
+        excluded.append(typeEntries.constFirst());
+    }
+    smartPointerEntry->setExcludedInstantiations(excluded);
+    return true;
+}
+
+bool TypeDatabasePrivate::resolveSmartPointerInstantiations(const TypeDatabaseParserContextPtr &context) const
 {
     const auto &instantiations = context->smartPointerInstantiations;
     for (auto it = instantiations.cbegin(), end = instantiations.cend(); it != end; ++it) {
-        const auto &smartPointerEntry = it.key();
-        const auto instantiationNames = splitTypeList(it.value());
-        SmartPointerTypeEntry::Instantiations instantiations;
-        instantiations.reserve(instantiationNames.size());
-        for (const auto &instantiation : instantiationNames) {
-            QString name;
-            QString type = instantiation;
-            const auto equalsPos = instantiation.indexOf(u'=');
-            if (equalsPos != -1) {
-                type.truncate(equalsPos);
-                name = instantiation.mid(equalsPos + 1);
-            }
-
-            const auto typeEntries = findCppTypes(type);
-            if (typeEntries.isEmpty()) {
-                const QString m = msgCannotFindTypeEntryForSmartPointer(type,
-                                                                        smartPointerEntry->name());
-                qCWarning(lcShiboken, "%s", qPrintable(m));
-                return false;
-            }
-            if (typeEntries.size() > 1) {
-                const QString m = msgAmbiguousTypesFound(type, typeEntries);
-                qCWarning(lcShiboken, "%s", qPrintable(m));
-                return false;
-            }
-            instantiations.append({name, typeEntries.constFirst()});
+        const auto &entry = it.value();
+        if ((!entry.instantiations.isEmpty()
+             && !resolveSmartPointerInstantiations(it.key(), entry.instantiations))
+            || (!entry.excludedInstantiations.isEmpty()
+                 && !resolveSmartPointerExclusions(it.key(), entry.excludedInstantiations))) {
+            return false;
         }
-        smartPointerEntry->setInstantiations(instantiations);
     }
     return true;
 }
@@ -1404,9 +1465,9 @@ bool TypeDatabase::setApiVersion(const QString& packageWildcardPattern, const QS
     if (versionNumber.isNull())
         return false;
     ApiVersions &versions = *apiVersions();
-    for (qsizetype i = 0, size = versions.size(); i < size; ++i) {
-        if (versions.at(i).first.pattern() == packagePattern) {
-            versions[i].second = versionNumber;
+    for (auto &version : versions) {
+        if (version.first.pattern() == packagePattern) {
+            version.second = versionNumber;
             return true;
         }
     }
@@ -1423,10 +1484,10 @@ bool TypeDatabase::checkApiVersion(const QString &package,
     const ApiVersions &versions = *apiVersions();
     if (versions.isEmpty()) // Nothing specified: use latest.
         return true;
-    for (qsizetype i = 0, size = versions.size(); i < size; ++i) {
-        if (versions.at(i).first.match(package).hasMatch())
-            return versions.at(i).second >= vr.since
-                && versions.at(i).second <= vr.until;
+    for (const auto &version : versions) {
+        if (version.first.match(package).hasMatch())
+            return version.second >= vr.since
+                && version.second <= vr.until;
     }
     return false;
 }
index cf7db5163f6d2747393397fb8a0029759654d567..ad3285e5caf94f97f1c8f67939f45aee36182257 100644 (file)
@@ -8,9 +8,9 @@
 #include "modifications_typedefs.h"
 #include "typedatabase_typedefs.h"
 
-#include <QtCore/QRegularExpression>
-#include <QtCore/QStringList>
-#include <QtCore/QVersionNumber>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qversionnumber.h>
 
 #include <memory>
 
@@ -60,6 +60,15 @@ struct TypeRejection
 QDebug operator<<(QDebug d, const TypeRejection &r);
 #endif
 
+// Rule for removing overloads that differ in one type
+struct OverloadRemovalRule
+{
+    QString type;
+    QStringList redundantTypes;
+};
+
+using OverloadRemovalRules = QList<OverloadRemovalRule>;
+
 class TypeDatabase
 {
     TypeDatabase();
@@ -161,6 +170,9 @@ public:
     FunctionModificationList
         globalFunctionModifications(const QStringList &signatures) const;
 
+    const OverloadRemovalRules &overloadRemovalRules() const;
+    void addOverloadRemovalRule(const OverloadRemovalRule &r);
+
     bool addSuppressedWarning(const QString &warning, bool generate, QString *errorMessage);
 
     bool isSuppressedWarning(QStringView s) const;
@@ -174,8 +186,8 @@ public:
 
     // Top level QIODevice parsing for tests.
     bool parseFile(QIODevice *device, bool generate = true);
-    bool parseFile(const std::shared_ptr<TypeDatabaseParserContext> &context,
-                   QIODevice *device, bool generate = true);
+    static bool parseFile(const std::shared_ptr<TypeDatabaseParserContext> &context,
+                          QIODevice *device, bool generate = true);
 
     static bool setApiVersion(const QString &package, const QString &version);
     static void clearApiVersions();
index fc56c79613ac0a28d2b40c5838f8e46262498bca..03656f7317833d468b39019f4cda53bf5e4338f0 100644 (file)
@@ -7,14 +7,20 @@
 #include "typesystem_typedefs.h"
 #include "containertypeentry.h"
 
-#include <QtCore/QHash>
-#include <QtCore/QString>
+#include <QtCore/qhash.h>
+#include <QtCore/qstring.h>
 
 class TypeDatabase;
 
 struct TypeDatabaseParserContext
 {
-    using SmartPointerInstantiations = QHash<SmartPointerTypeEntryPtr, QString>;
+    struct SmartPointerEntry
+    {
+        QString instantiations;
+        QString excludedInstantiations;
+    };
+
+    using SmartPointerInstantiations = QHash<SmartPointerTypeEntryPtr, SmartPointerEntry>;
     using OpaqueContainerHash = QHash<QString, OpaqueContainers>;
 
     TypeDatabase *db;
index f00c61570d998e905ba6f1c47d69677618a4d2e2..77eb23a9de8985970927d62532768ecdfc66e5aa 100644 (file)
@@ -6,9 +6,9 @@
 
 #include "typesystem_typedefs.h"
 
-#include <QtCore/QMultiMap>
-#include <QtCore/QString>
-#include <QtCore/QList>
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qlist.h>
 
 using TemplateEntryMap =QMap<QString, TemplateEntryPtr>;
 
index 44646972c726ad7950405155e04d3bc962c495a9..dfa19cdd7a86e2496ab0ab6a319f8254cb2cad7f 100644 (file)
@@ -25,7 +25,7 @@ public:
     void setSource(const ComplexTypeEntryCPtr &source);
 
     ComplexTypeEntryPtr target() const;
-    void setTarget(ComplexTypeEntryPtr target);
+    void setTarget(const ComplexTypeEntryPtr &target);
 
 #ifndef QT_NO_DEBUG_STREAM
     void formatDebug(QDebug &d) const override;
index bcf2621d223fb21a02f4f78ae2d10f834b98eba7..4dd5994dafa312ac66672d238b9a5f05c7909b22 100644 (file)
@@ -4,9 +4,9 @@
 #include "typeparser.h"
 #include <typeinfo.h>
 
-#include <QtCore/QDebug>
-#include <QtCore/QStack>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qtextstream.h>
 
 using namespace Qt::StringLiterals;
 
@@ -138,6 +138,8 @@ Scanner::Token Scanner::nextToken(QString *errorMessage)
                 tok = VolatileToken;
             }
             break;
+        default:
+            break;
         }
     }
 
index 97634b5db5176dd7e25dd3686f41b8736f62f9e9..70f33c3b93fb8fa048571ef89e45d4166f1dd9e6 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TYPEPARSER_H
 #define TYPEPARSER_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 class TypeInfo;
 
index 98b82cb46150feb6c2c964758c1af7f53652436f..0820150f894dc4cba7f5f6f59de180127d6c2e19 100644 (file)
@@ -4,6 +4,7 @@
 #include "typesystem.h"
 #include "arraytypeentry.h"
 #include "codesnip.h"
+#include "cpptypeentry.h"
 #include "complextypeentry.h"
 #include "configurabletypeentry.h"
 #include "constantvaluetypeentry.h"
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QSet>
-#include <QtCore/QVarLengthArray>
+#include <QtCore/qdebug.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qset.h>
+#include <QtCore/qvarlengtharray.h>
 
 using namespace Qt::StringLiterals;
 
@@ -75,7 +76,6 @@ public:
     QVersionNumber m_version;
     SourceLocation m_sourceLocation; // XML file
     TypeEntry::CodeGeneration m_codeGeneration = TypeEntry::GenerateCode;
-    TypeEntryPtr m_viewOn;
     CustomTypeEntryPtr m_targetLangApiType;
     int m_revision = 0;
     int m_sbkIndex = 0;
@@ -544,16 +544,6 @@ bool TypeEntry::isComplex() const
     return false;
 }
 
-TypeEntryPtr TypeEntry::viewOn() const
-{
-    return m_d->m_viewOn;
-}
-
-void TypeEntry::setViewOn(const TypeEntryPtr &v)
-{
-    m_d->m_viewOn = v;
-}
-
 TypeEntry *TypeEntry::clone() const
 {
     return new TypeEntry(new TypeEntryPrivate(*m_d.data()));
@@ -573,23 +563,187 @@ void TypeEntry::useAsTypedef(const TypeEntryCPtr &source)
     m_d->m_version = source->m_d->m_version;
 }
 
-// ----------------- CustomTypeEntry
-class  CustomTypeEntryPrivate : public TypeEntryPrivate
+// ----------------- CppTypeEntry
+
+class CppTypeEntryPrivate : public TypeEntryPrivate
 {
 public:
     using TypeEntryPrivate::TypeEntryPrivate;
 
+    CppTypeEntryCPtr m_viewOn;
+    QString m_defaultConstructor;
+    TypeSystem::CopyableFlag m_copyableFlag = TypeSystem::CopyableFlag::Unspecified;
+    bool m_copyableDetected = true;
+    TypeSystem::DefaultConstructibleFlag m_defaultConstructibleFlag = TypeSystem::DefaultConstructibleFlag::Unspecified;
+    bool m_defaultConstructibleDetected = true;
+    TypeSystem::MovableFlag m_movableFlag = TypeSystem::MovableFlag::Unspecified;
+    bool m_movableDetected = true;
+    TypeSystem::QtMetaTypeRegistration m_qtMetaTypeRegistration =
+        TypeSystem::QtMetaTypeRegistration::Unspecified;
+};
+
+CppTypeEntry::CppTypeEntry(const QString &entryName, Type t,
+                           const QVersionNumber &vr,
+                           const TypeEntryCPtr &parent) :
+    TypeEntry(new CppTypeEntryPrivate(entryName, t, vr, parent))
+{
+}
+
+CppTypeEntry::CppTypeEntry(CppTypeEntryPrivate *d) :
+    TypeEntry(d)
+{
+}
+
+const QString &CppTypeEntry::defaultConstructor() const
+{
+    S_D(const CppTypeEntry);
+    return d->m_defaultConstructor;
+}
+
+void CppTypeEntry::setDefaultConstructor(const QString& defaultConstructor)
+{
+    S_D(CppTypeEntry);
+    d->m_defaultConstructor = defaultConstructor;
+}
+
+CppTypeEntryCPtr CppTypeEntry::viewOn() const
+{
+    S_D(const CppTypeEntry);
+    return d->m_viewOn;
+}
+
+void CppTypeEntry::setViewOn(const CppTypeEntryCPtr &v)
+{
+    S_D(CppTypeEntry);
+    d->m_viewOn = v;
+}
+
+TypeSystem::DefaultConstructibleFlag CppTypeEntry::defaultConstructibleFlag() const
+{
+    S_D(const CppTypeEntry);
+    return d->m_defaultConstructibleFlag;
+}
+
+void CppTypeEntry::setDefaultConstructibleFlag(TypeSystem::DefaultConstructibleFlag flag)
+{
+    S_D(CppTypeEntry);
+    d->m_defaultConstructibleFlag = flag;
+}
+
+bool CppTypeEntry::isDefaultConstructible() const
+{
+    S_D(const CppTypeEntry);
+    switch (d->m_defaultConstructibleFlag) {
+    case TypeSystem::DefaultConstructibleFlag::Enabled:
+        return true;
+    case TypeSystem::DefaultConstructibleFlag::Disabled:
+         return false;
+    case TypeSystem::DefaultConstructibleFlag::Unspecified:
+        break;
+    }
+    return d->m_defaultConstructibleDetected || !d->m_defaultConstructor.isEmpty();
+}
+
+void CppTypeEntry::setDefaultConstructibleDetected(bool c)
+{
+    S_D(CppTypeEntry);
+    d->m_defaultConstructibleDetected = c;
+}
+
+TypeSystem::CopyableFlag CppTypeEntry::copyableFlag() const
+{
+    S_D(const CppTypeEntry);
+    return d->m_copyableFlag;
+}
+
+void CppTypeEntry::setCopyableFlag(TypeSystem::CopyableFlag flag)
+{
+    S_D(CppTypeEntry);
+    d->m_copyableFlag = flag;
+}
+
+bool CppTypeEntry::isCopyable() const
+{
+    S_D(const CppTypeEntry);
+    switch (d->m_copyableFlag) {
+    case TypeSystem::CopyableFlag::Enabled:
+        return true;
+    case TypeSystem::CopyableFlag::Disabled:
+        return false;
+    case TypeSystem::CopyableFlag::Unspecified:
+        break;
+    }
+    return d->m_copyableDetected;
+}
+
+void CppTypeEntry::setCopyableDetected(bool c)
+{
+    S_D(CppTypeEntry);
+    d->m_copyableDetected = c;
+}
+
+// Movable has no detection logic in the code model (yet). It is turned off for namespaces
+TypeSystem::MovableFlag CppTypeEntry::movableFlag() const
+{
+    S_D(const CppTypeEntry);
+    return d->m_movableFlag;
+}
+
+void CppTypeEntry::setMovableFlag(TypeSystem::MovableFlag flag)
+{
+    S_D(CppTypeEntry);
+    d->m_movableFlag = flag;
+}
+
+bool CppTypeEntry::isMovable() const
+{
+    S_D(const CppTypeEntry);
+    switch (d->m_movableFlag) {
+    case TypeSystem::MovableFlag::Enabled:
+        return true;
+    case TypeSystem::MovableFlag::Disabled:
+        return false;
+    case TypeSystem::MovableFlag::Unspecified:
+        break;
+    }
+    return d->m_movableDetected;
+}
+
+TypeSystem::QtMetaTypeRegistration CppTypeEntry::qtMetaTypeRegistration() const
+{
+    S_D(const CppTypeEntry);
+    return d->m_qtMetaTypeRegistration;
+}
+
+void CppTypeEntry::setQtMetaTypeRegistration(TypeSystem::QtMetaTypeRegistration r)
+{
+    S_D(CppTypeEntry);
+    d->m_qtMetaTypeRegistration = r;
+}
+
+TypeEntry *CppTypeEntry::clone() const
+{
+    S_D(const CppTypeEntry);
+    return new CppTypeEntry(new CppTypeEntryPrivate(*d));
+}
+
+// ----------------- CustomTypeEntry
+class CustomTypeEntryPrivate : public CppTypeEntryPrivate
+{
+public:
+    using CppTypeEntryPrivate::CppTypeEntryPrivate;
+
     QString m_checkFunction;
 };
 
 CustomTypeEntry::CustomTypeEntry(const QString &entryName, const QVersionNumber &vr,
                                  const TypeEntryCPtr &parent) :
-    TypeEntry(new CustomTypeEntryPrivate(entryName, CustomType, vr, parent))
+    CppTypeEntry(new CustomTypeEntryPrivate(entryName, CustomType, vr, parent))
 {
 }
 
-CustomTypeEntry::CustomTypeEntry(TypeEntryPrivate *d) :
-    TypeEntry(d)
+CustomTypeEntry::CustomTypeEntry(CustomTypeEntryPrivate *d) :
+    CppTypeEntry(d)
 {
 }
 
@@ -653,7 +807,7 @@ TypeSystem::CPythonType PythonTypeEntry::cPythonType() const
     return d->m_cPythonType;
 }
 
-PythonTypeEntry::PythonTypeEntry(TypeEntryPrivate *d) :
+PythonTypeEntry::PythonTypeEntry(CustomTypeEntryPrivate *d) :
     CustomTypeEntry(d)
 {
 }
@@ -788,18 +942,19 @@ void TypeSystemTypeEntry::setSnakeCase(TypeSystem::SnakeCase sc)
 
 // ----------------- VoidTypeEntry
 VoidTypeEntry::VoidTypeEntry() :
-    TypeEntry(u"void"_s, VoidType, QVersionNumber(0, 0), nullptr)
+    CppTypeEntry(u"void"_s, VoidType, QVersionNumber(0, 0), nullptr)
 {
 }
 
-VoidTypeEntry::VoidTypeEntry(TypeEntryPrivate *d) :
-    TypeEntry(d)
+VoidTypeEntry::VoidTypeEntry(CppTypeEntryPrivate *d) :
+    CppTypeEntry(d)
 {
 }
 
 TypeEntry *VoidTypeEntry::clone() const
 {
-    return new VoidTypeEntry(new TypeEntryPrivate(*d_func()));
+    S_D(const CppTypeEntry);
+    return new VoidTypeEntry(new CppTypeEntryPrivate(*d));
 }
 
 VarargsTypeEntry::VarargsTypeEntry() :
@@ -857,12 +1012,12 @@ TemplateArgumentEntry::TemplateArgumentEntry(TemplateArgumentEntryPrivate *d) :
 }
 
 // ----------------- ArrayTypeEntry
-class ArrayTypeEntryPrivate : public TypeEntryPrivate
+class ArrayTypeEntryPrivate : public CppTypeEntryPrivate
 {
 public:
     explicit ArrayTypeEntryPrivate(const TypeEntryCPtr &nested_type, const QVersionNumber &vr,
                                    const TypeEntryCPtr &parent) :
-        TypeEntryPrivate(u"Array"_s, TypeEntry::ArrayType, vr, parent),
+        CppTypeEntryPrivate(u"Array"_s, TypeEntry::ArrayType, vr, parent),
         m_nestedType(nested_type)
     {
     }
@@ -872,7 +1027,7 @@ public:
 
 ArrayTypeEntry::ArrayTypeEntry(const TypeEntryCPtr &nested_type, const QVersionNumber &vr,
                                const TypeEntryCPtr &parent) :
-    TypeEntry(new ArrayTypeEntryPrivate(nested_type, vr, parent))
+    CppTypeEntry(new ArrayTypeEntryPrivate(nested_type, vr, parent))
 {
     Q_ASSERT(nested_type);
 }
@@ -902,22 +1057,21 @@ TypeEntry *ArrayTypeEntry::clone() const
 }
 
 ArrayTypeEntry::ArrayTypeEntry(ArrayTypeEntryPrivate *d) :
-    TypeEntry(d)
+    CppTypeEntry(d)
 {
 }
 
 // ----------------- PrimitiveTypeEntry
-class PrimitiveTypeEntryPrivate : public TypeEntryPrivate
+class PrimitiveTypeEntryPrivate : public CppTypeEntryPrivate
 {
 public:
     PrimitiveTypeEntryPrivate(const QString &entryName, const QVersionNumber &vr,
                               const TypeEntryCPtr &parent) :
-        TypeEntryPrivate(entryName, TypeEntry::PrimitiveType, vr, parent),
+        CppTypeEntryPrivate(entryName, TypeEntry::PrimitiveType, vr, parent),
         m_preferredTargetLangType(true)
     {
     }
 
-    QString m_defaultConstructor;
     CustomConversionPtr m_customConversion;
     PrimitiveTypeEntryPtr m_referencedTypeEntry;
     uint m_preferredTargetLangType : 1;
@@ -925,26 +1079,8 @@ public:
 
 PrimitiveTypeEntry::PrimitiveTypeEntry(const QString &entryName, const QVersionNumber &vr,
                                        const TypeEntryCPtr &parent) :
-    TypeEntry(new PrimitiveTypeEntryPrivate(entryName, vr, parent))
-{
-}
-
-QString PrimitiveTypeEntry::defaultConstructor() const
-{
-    S_D(const PrimitiveTypeEntry);
-    return d->m_defaultConstructor;
-}
-
-void PrimitiveTypeEntry::setDefaultConstructor(const QString &defaultConstructor)
-{
-    S_D(PrimitiveTypeEntry);
-    d->m_defaultConstructor = defaultConstructor;
-}
-
-bool PrimitiveTypeEntry::hasDefaultConstructor() const
+    CppTypeEntry(new PrimitiveTypeEntryPrivate(entryName, vr, parent))
 {
-    S_D(const PrimitiveTypeEntry);
-    return !d->m_defaultConstructor.isEmpty();
 }
 
 PrimitiveTypeEntryPtr PrimitiveTypeEntry::referencedTypeEntry() const
@@ -953,7 +1089,7 @@ PrimitiveTypeEntryPtr PrimitiveTypeEntry::referencedTypeEntry() const
     return d->m_referencedTypeEntry;
 }
 
-void PrimitiveTypeEntry::setReferencedTypeEntry(PrimitiveTypeEntryPtr referencedTypeEntry)
+void PrimitiveTypeEntry::setReferencedTypeEntry(const PrimitiveTypeEntryPtr &referencedTypeEntry)
 {
     S_D(PrimitiveTypeEntry);
     d->m_referencedTypeEntry = referencedTypeEntry;
@@ -1026,16 +1162,16 @@ TypeEntry *PrimitiveTypeEntry::clone() const
 }
 
 PrimitiveTypeEntry::PrimitiveTypeEntry(PrimitiveTypeEntryPrivate *d)
-    : TypeEntry(d)
+    : CppTypeEntry(d)
 {
 }
 
 // ----------------- ConfigurableTypeEntry
 
-class ConfigurableTypeEntryPrivate : public TypeEntryPrivate
+class ConfigurableTypeEntryPrivate : public CppTypeEntryPrivate
 {
 public:
-    using TypeEntryPrivate::TypeEntryPrivate;
+    using CppTypeEntryPrivate::CppTypeEntryPrivate;
 
     QString m_configCondition;
 };
@@ -1043,12 +1179,12 @@ public:
 ConfigurableTypeEntry::ConfigurableTypeEntry(const QString &entryName, Type t,
                                              const QVersionNumber &vr,
                                              const TypeEntryCPtr &parent) :
-    TypeEntry(new ConfigurableTypeEntryPrivate(entryName, t, vr, parent))
+    CppTypeEntry(new ConfigurableTypeEntryPrivate(entryName, t, vr, parent))
 {
 }
 
 ConfigurableTypeEntry::ConfigurableTypeEntry(ConfigurableTypeEntryPrivate *d) :
-    TypeEntry(d)
+    CppTypeEntry(d)
 {
 }
 
@@ -1256,10 +1392,10 @@ EnumValueTypeEntry::EnumValueTypeEntry(EnumValueTypeEntryPrivate *d) :
 }
 
 // ----------------- FlagsTypeEntry
-class FlagsTypeEntryPrivate : public TypeEntryPrivate
+class FlagsTypeEntryPrivate : public CppTypeEntryPrivate
 {
 public:
-    using TypeEntryPrivate::TypeEntryPrivate;
+    using CppTypeEntryPrivate::CppTypeEntryPrivate;
 
     QString m_originalName;
     QString m_flagsName;
@@ -1268,7 +1404,7 @@ public:
 
 FlagsTypeEntry::FlagsTypeEntry(const QString &entryName, const QVersionNumber &vr,
                                const TypeEntryCPtr &parent) :
-    TypeEntry(new FlagsTypeEntryPrivate(entryName, FlagsType, vr, parent))
+    CppTypeEntry(new FlagsTypeEntryPrivate(entryName, FlagsType, vr, parent))
 {
 }
 
@@ -1281,7 +1417,7 @@ QString FlagsTypeEntry::buildTargetLangName() const
 }
 
 FlagsTypeEntry::FlagsTypeEntry(FlagsTypeEntryPrivate *d) :
-    TypeEntry(d)
+    CppTypeEntry(d)
 {
 }
 
@@ -1364,7 +1500,6 @@ public:
     FieldModificationList m_fieldMods;
     QList<TypeSystemProperty> m_properties;
     QList<TypeSystemPyMethodDefEntry> m_PyMethodDefEntrys;
-    QString m_defaultConstructor;
     QString m_defaultSuperclass;
     QString m_qualifiedCppName;
     QString m_docFile;
@@ -1377,7 +1512,6 @@ public:
     QString m_polymorphicNameFunction;
     QString m_targetType;
     ComplexTypeEntry::TypeFlags m_typeFlags;
-    ComplexTypeEntry::CopyableFlag m_copyableFlag = ComplexTypeEntry::Unknown;
     QString m_hashFunction;
 
     ComplexTypeEntryCPtr m_baseContainerType;
@@ -1389,8 +1523,6 @@ public:
     TypeSystem::BoolCast m_isNullMode = TypeSystem::BoolCast::Unspecified;
     TypeSystem::QtMetaTypeRegistration m_qtMetaTypeRegistration =
         TypeSystem::QtMetaTypeRegistration::Unspecified;
-    // Determined by AbstractMetaBuilder from the code model.
-    bool m_isValueTypeWithCopyConstructorOnly = false;
 };
 
 ComplexTypeEntry::ComplexTypeEntry(const QString &entryName, TypeEntry::Type t,
@@ -1686,30 +1818,6 @@ void ComplexTypeEntry::setDeleteInMainThread(bool dmt)
     d->m_deleteInMainThread = dmt;
 }
 
-ComplexTypeEntry::CopyableFlag ComplexTypeEntry::copyable() const
-{
-    S_D(const ComplexTypeEntry);
-    return d->m_copyableFlag;
-}
-
-void ComplexTypeEntry::setCopyable(ComplexTypeEntry::CopyableFlag flag)
-{
-    S_D(ComplexTypeEntry);
-    d->m_copyableFlag = flag;
-}
-
-TypeSystem::QtMetaTypeRegistration ComplexTypeEntry::qtMetaTypeRegistration() const
-{
-    S_D(const ComplexTypeEntry);
-    return d->m_qtMetaTypeRegistration;
-}
-
-void ComplexTypeEntry::setQtMetaTypeRegistration(TypeSystem::QtMetaTypeRegistration r)
-{
-    S_D(ComplexTypeEntry);
-    d->m_qtMetaTypeRegistration = r;
-}
-
 QString ComplexTypeEntry::hashFunction() const
 {
     S_D(const ComplexTypeEntry);
@@ -1758,24 +1866,6 @@ void ComplexTypeEntry::setAllowThread(TypeSystem::AllowThread allowThread)
     d->m_allowThread = allowThread;
 }
 
-void ComplexTypeEntry::setDefaultConstructor(const QString& defaultConstructor)
-{
-    S_D(ComplexTypeEntry);
-    d->m_defaultConstructor = defaultConstructor;
-}
-
-QString ComplexTypeEntry::defaultConstructor() const
-{
-    S_D(const ComplexTypeEntry);
-    return d->m_defaultConstructor;
-}
-
-bool ComplexTypeEntry::hasDefaultConstructor() const
-{
-    S_D(const ComplexTypeEntry);
-    return !d->m_defaultConstructor.isEmpty();
-}
-
 TypeSystem::SnakeCase ComplexTypeEntry::snakeCase() const
 {
     S_D(const ComplexTypeEntry);
@@ -1788,18 +1878,6 @@ void ComplexTypeEntry::setSnakeCase(TypeSystem::SnakeCase sc)
     d->m_snakeCase = sc;
 }
 
-bool ComplexTypeEntry::isValueTypeWithCopyConstructorOnly() const
-{
-    S_D(const ComplexTypeEntry);
-    return d->m_isValueTypeWithCopyConstructorOnly;
-}
-
-void ComplexTypeEntry::setValueTypeWithCopyConstructorOnly(bool v)
-{
-    S_D(ComplexTypeEntry);
-    d->m_isValueTypeWithCopyConstructorOnly = v;
-}
-
 QString ComplexTypeEntry::docFile() const
 {
     S_D(const ComplexTypeEntry);
@@ -1915,7 +1993,7 @@ ComplexTypeEntryPtr TypedefEntry::target() const
     return d->m_target;
 }
 
-void TypedefEntry::setTarget(ComplexTypeEntryPtr target)
+void TypedefEntry::setTarget(const ComplexTypeEntryPtr &target)
 {
     S_D(TypedefEntry);
     d->m_target = target;
@@ -2075,6 +2153,7 @@ public:
     QString m_nullCheckMethod;
     QString m_resetMethod;
     SmartPointerTypeEntry::Instantiations m_instantiations;
+    TypeEntryCList m_excludedInstantiations;
     TypeSystem::SmartPointerType m_smartPointerType;
 };
 
@@ -2096,6 +2175,11 @@ SmartPointerTypeEntry::SmartPointerTypeEntry(const QString &entryName,
     ComplexTypeEntry(new SmartPointerTypeEntryPrivate(entryName, getterName, smartPointerType,
                                                       refCountMethodName, vr, parent))
 {
+    S_D(SmartPointerTypeEntry);
+    // Pre-set detection values which is not done by the code model since
+    // the clang parser might not see all system library includes.
+    d->m_defaultConstructibleDetected = smartPointerType != TypeSystem::SmartPointerType::Handle;
+    d->m_copyableDetected = smartPointerType != TypeSystem::SmartPointerType::Unique;
 }
 
 TypeSystem::SmartPointerType SmartPointerTypeEntry::smartPointerType() const
@@ -2170,6 +2254,18 @@ void SmartPointerTypeEntry::setInstantiations(const Instantiations &i)
     d->m_instantiations = i;
 }
 
+void SmartPointerTypeEntry::setExcludedInstantiations(const TypeEntryCList  &ex)
+{
+    S_D(SmartPointerTypeEntry);
+    d->m_excludedInstantiations = ex;
+}
+
+const TypeEntryCList &SmartPointerTypeEntry::excludedInstantiations() const
+{
+    S_D(const SmartPointerTypeEntry);
+    return d->m_excludedInstantiations;
+}
+
 SmartPointerTypeEntry::SmartPointerTypeEntry(SmartPointerTypeEntryPrivate *d) :
     ComplexTypeEntry(d)
 {
@@ -2179,7 +2275,8 @@ bool SmartPointerTypeEntry::matchesInstantiation(const TypeEntryCPtr &e) const
 {
     S_D(const SmartPointerTypeEntry);
     // No instantiations specified, or match
-    return d->m_instantiations.isEmpty() || d->instantiationIndex(e) != -1;
+    return !d->m_excludedInstantiations.contains(e)
+        && (d->m_instantiations.isEmpty() || d->instantiationIndex(e) != -1);
 }
 
 static QString fixSmartPointerName(QString name)
@@ -2227,6 +2324,8 @@ NamespaceTypeEntry::NamespaceTypeEntry(const QString &entryName, const QVersionN
                                        const TypeEntryCPtr &parent) :
     ComplexTypeEntry(new NamespaceTypeEntryPrivate(entryName, NamespaceType, vr, parent))
 {
+    S_D(NamespaceTypeEntry);
+    d->m_copyableDetected = d->m_defaultConstructibleDetected = d->m_movableDetected = false;
 }
 
 TypeEntry *NamespaceTypeEntry::clone() const
@@ -2471,6 +2570,8 @@ ObjectTypeEntry::ObjectTypeEntry(const QString &entryName, const QVersionNumber
                                  const TypeEntryCPtr &parent)
     : ComplexTypeEntry(entryName, ObjectType, vr, parent)
 {
+    S_D(ComplexTypeEntry);
+    d->m_defaultConstructibleDetected = d->m_copyableDetected = false;
 }
 
 TypeEntry *ObjectTypeEntry::clone() const
@@ -2512,8 +2613,6 @@ void TypeEntry::formatDebug(QDebug &debug) const
     FORMAT_NONEMPTY_STRING("package", m_d->m_targetLangPackage)
     FORMAT_BOOL("stream", m_d->m_stream)
     FORMAT_BOOL("built-in", m_d->m_builtin)
-    if (m_d->m_viewOn)
-       debug << ", views=" << m_d->m_viewOn->name();
     if (!m_d->m_version.isNull() && m_d->m_version > QVersionNumber(0, 0))
         debug << ", version=" << m_d->m_version;
     if (m_d->m_revision)
@@ -2527,6 +2626,19 @@ void TypeEntry::formatDebug(QDebug &debug) const
     formatList(debug, "extraIncludes", m_d->m_extraIncludes, ", ");
 }
 
+void CppTypeEntry::formatDebug(QDebug &debug) const
+{
+    S_D(const CppTypeEntry);
+    if (d->m_viewOn)
+        debug << ", views=" << d->m_viewOn->name();
+    if (isDefaultConstructible())
+        debug << ", [default constructible]";
+    if (isCopyable())
+        debug << ", [copyable]";
+    if (isMovable())
+        debug << ", [movable]";
+}
+
 void PrimitiveTypeEntry::formatDebug(QDebug &debug) const
 {
     TypeEntry::formatDebug(debug);
@@ -2547,8 +2659,7 @@ void ComplexTypeEntry::formatDebug(QDebug &debug) const
     FORMAT_BOOL("deleteInMainThread", d->m_deleteInMainThread)
     if (d->m_typeFlags != 0)
         debug << ", typeFlags=" << d->m_typeFlags;
-    debug << ", copyableFlag=" << d->m_copyableFlag
-        << ", except=" << int(d->m_exceptionHandling)
+    debug << ", except=" << int(d->m_exceptionHandling)
         << ", snakeCase=" << int(d->m_snakeCase);
     FORMAT_NONEMPTY_STRING("defaultSuperclass", d->m_defaultSuperclass)
     FORMAT_NONEMPTY_STRING("polymorphicIdValue", d->m_polymorphicIdValue)
index a2e4debc88b26f3f3a5063fe50fa8fd99feb8c5e..b34d860b0c7680511a2f6a275ec7438dd0ce5f63 100644 (file)
@@ -8,8 +8,8 @@
 #include "typesystem_typedefs.h"
 
 #include <QtCore/qobjectdefs.h>
-#include <QtCore/QString>
-#include <QtCore/QScopedPointer>
+#include <QtCore/qstring.h>
+#include <QtCore/qscopedpointer.h>
 
 class AbstractMetaType;
 class CustomTypeEntry;
@@ -158,12 +158,6 @@ public:
 
     QVersionNumber version() const;
 
-    // View on: Type to use for function argument conversion, fex
-    // std::string_view -> std::string for foo(std::string_view).
-    // cf AbstractMetaType::viewOn()
-    TypeEntryPtr viewOn() const;
-    void setViewOn(const TypeEntryPtr &v);
-
     virtual TypeEntry *clone() const;
 
     void useAsTypedef(const TypeEntryCPtr &source);
index e2d86cb92bf1faef3654314d33778dd108fbc767..c0c3da1f64f7b11ce5ea38ee6f42c250d2ac29f4 100644 (file)
@@ -111,6 +111,24 @@ enum class PythonEnumType {
     IntFlag
 };
 
+enum class DefaultConstructibleFlag : unsigned char {
+    Unspecified,
+    Enabled,
+    Disabled
+};
+
+enum class CopyableFlag : unsigned char {
+    Unspecified,
+    Enabled,
+    Disabled
+};
+
+enum class MovableFlag : unsigned char {
+    Unspecified,
+    Enabled,
+    Disabled
+};
+
 enum : int { OverloadNumberUnset = -1, OverloadNumberDefault = 99999 };
 
 } // namespace TypeSystem
index 5a4e12ff221138e8cae659f69e2d5cf78e75c5d8..ddcfa54c9e083743f7d271d3c8a806e93764c2ba 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef TYPESYSTEM_TYPEDEFS_H
 #define TYPESYSTEM_TYPEDEFS_H
 
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
 
 #include <memory>
 
@@ -13,6 +13,7 @@ class ComplexTypeEntry;
 class ConfigurableTypeEntry;
 class ConstantValueTypeEntry;
 class ContainerTypeEntry;
+class CppTypeEntry;
 class CustomTypeEntry;
 class EnumTypeEntry;
 class EnumValueTypeEntry;
@@ -33,6 +34,7 @@ using ComplexTypeEntryPtr = std::shared_ptr<ComplexTypeEntry>;
 using ConfigurableTypeEntryPtr = std::shared_ptr<ConfigurableTypeEntry>;
 using ConstantValueTypeEntryPtr = std::shared_ptr<ConstantValueTypeEntry>;
 using ContainerTypeEntryPtr = std::shared_ptr<ContainerTypeEntry>;
+using CppTypeEntryPtr = std::shared_ptr<CppTypeEntry>;
 using CustomTypeEntryPtr = std::shared_ptr<CustomTypeEntry>;
 using EnumTypeEntryPtr = std::shared_ptr<EnumTypeEntry>;
 using EnumValueTypeEntryPtr = std::shared_ptr<EnumValueTypeEntry>;
@@ -53,6 +55,7 @@ using ComplexTypeEntryCPtr = std::shared_ptr<const ComplexTypeEntry>;
 using ConstantValueTypeEntryCPtr = std::shared_ptr<const ConstantValueTypeEntry>;
 using ConfigurableTypeEntryCPtr = std::shared_ptr<const ConfigurableTypeEntry>;
 using ContainerTypeEntryCPtr = std::shared_ptr<const ContainerTypeEntry>;
+using CppTypeEntryCPtr = std::shared_ptr<const CppTypeEntry>;
 using CustomTypeEntryCPtr = std::shared_ptr<const CustomTypeEntry>;
 using EnumTypeEntryCPtr = std::shared_ptr<const EnumTypeEntry>;
 using EnumValueTypeEntryCPtr = std::shared_ptr<const EnumValueTypeEntry>;
index a4887ab23f8cc16117f16cc16e923bb1694e80a2..1d747419f5484973b42625918b66bfc6b9412fd5 100644 (file)
@@ -10,6 +10,7 @@
 #include "customconversion.h"
 #include "customtypenentry.h"
 #include "documentation_enums.h"
+#include "filecache.h"
 #include "flagstypeentry.h"
 #include "functiontypeentry.h"
 #include "namespacetypeentry.h"
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfo>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QSet>
-#include <QtCore/QStringView>
-#include <QtCore/QStringAlgorithms>
-#include <QtCore/QVersionNumber>
-#include <QtCore/QXmlStreamAttributes>
-#include <QtCore/QXmlStreamReader>
-#include <QtCore/QXmlStreamEntityResolver>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstringview.h>
+#include <QtCore/qstringalgorithms.h>
+#include <QtCore/qversionnumber.h>
+#include <QtCore/qxmlstream.h>
 
 #include <algorithm>
 #include <optional>
@@ -49,7 +48,9 @@ using namespace Qt::StringLiterals;
 
 constexpr auto allowThreadAttribute = "allow-thread"_L1;
 constexpr auto checkFunctionAttribute = "check-function"_L1;
+constexpr auto defaultConstructibleAttribute = "default-constructible"_L1;
 constexpr auto copyableAttribute = "copyable"_L1;
+constexpr auto movableAttribute = "movable"_L1;
 constexpr auto accessAttribute = "access"_L1;
 constexpr auto actionAttribute = "action"_L1;
 constexpr auto quoteAfterLineAttribute = "quote-after-line"_L1;
@@ -172,34 +173,14 @@ static inline bool hasFileSnippetAttributes(const QXmlStreamAttributes *attribut
     return attributes->hasAttribute(fileAttribute);
 }
 
-// Extract a snippet from a file within annotation "// @snippet label".
-std::optional<QString>
-    extractSnippet(const QString &code, const QString &snippetLabel)
+static QRegularExpression snippetPattern(const QString &snippetLabel)
 {
-    if (snippetLabel.isEmpty())
-        return code;
     const QString pattern = R"(^\s*//\s*@snippet\s+)"_L1
-        + QRegularExpression::escape(snippetLabel)
-        + R"(\s*$)"_L1;
-    const QRegularExpression snippetRe(pattern);
-    Q_ASSERT(snippetRe.isValid());
-
-    bool useLine = false;
-    bool foundLabel = false;
-    QString result;
-    const auto lines = QStringView{code}.split(u'\n');
-    for (const auto &line : lines) {
-        if (snippetRe.matchView(line).hasMatch()) {
-            foundLabel = true;
-            useLine = !useLine;
-            if (!useLine)
-                break; // End of snippet reached
-        } else if (useLine)
-            result += line.toString() + u'\n';
-    }
-    if (!foundLabel)
-        return {};
-    return CodeSnipAbstract::fixSpaces(result);
+                            + QRegularExpression::escape(snippetLabel)
+                            + R"(\s*$)"_L1;
+    QRegularExpression result(pattern);
+    Q_ASSERT(result.isValid());
+    return result;
 }
 
 template <class EnumType>
@@ -382,6 +363,14 @@ ENUM_LOOKUP_BEGIN(TypeSystem::DocModificationMode, Qt::CaseInsensitive,
     };
 ENUM_LOOKUP_LINEAR_SEARCH
 
+ENUM_LOOKUP_BEGIN(DocumentationTarget, Qt::CaseSensitive,
+                  docTargetFromAttribute)
+    {
+        {u"documentation", DocumentationTarget::Documentation},
+        {u"docstring", DocumentationTarget::DocString}
+    };
+ENUM_LOOKUP_LINEAR_SEARCH
+
 ENUM_LOOKUP_BEGIN(ContainerTypeEntry::ContainerKind, Qt::CaseSensitive,
                   containerTypeFromAttribute)
     {
@@ -489,6 +478,7 @@ static const StackElementHash &stackElementHash()
         {u"no-null-pointer", StackElement::NoNullPointers},
         {u"object-type", StackElement::ObjectTypeEntry},
         {u"opaque-container", StackElement::OpaqueContainer},
+        {u"overload-removal", StackElement::OverloadRemoval},
         {u"parent", StackElement::ParentOwner},
         {u"primitive-type", StackElement::PrimitiveTypeEntry},
         {u"property", StackElement::Property},
@@ -557,8 +547,8 @@ ENUM_LOOKUP_BEGIN(TypeSystem::Visibility, Qt::CaseSensitive,
 };
 ENUM_LOOKUP_LINEAR_SEARCH
 
-static int indexOfAttribute(const QXmlStreamAttributes &atts,
-                            QAnyStringView name)
+static qsizetype indexOfAttribute(const QXmlStreamAttributes &atts,
+                                  QAnyStringView name)
 {
     for (qsizetype i = 0, size = atts.size(); i < size; ++i) {
         if (atts.at(i).qualifiedName() == name)
@@ -999,8 +989,10 @@ bool TypeSystemParser::endElement(StackElement element)
         m_templateEntry = nullptr;
         break;
     case StackElement::InsertTemplate:
-        if (auto *snip = injectCodeTarget(1))
-            snip->addTemplateInstance(m_templateInstance);
+        if (auto *snip = injectCodeTarget(1)) {
+            Q_ASSERT(m_templateInstance.has_value());
+            snip->addTemplateInstance(m_templateInstance.value());
+        }
         m_templateInstance.reset();
         break;
 
@@ -1277,7 +1269,7 @@ bool TypeSystemParser::checkRootElement()
     return false;
 }
 
-static TypeEntryPtr findViewedType(const QString &name)
+static CppTypeEntryCPtr findViewedType(const QString &name)
 {
     const auto range = TypeDatabase::instance()->entries().equal_range(name);
     for (auto i = range.first; i != range.second; ++i) {
@@ -1286,7 +1278,7 @@ static TypeEntryPtr findViewedType(const QString &name)
         case TypeEntry::PrimitiveType:
         case TypeEntry::ContainerType:
         case TypeEntry::ObjectType:
-            return i.value();
+            return std::dynamic_pointer_cast<const CppTypeEntry>(i.value());
         default:
             break;
         }
@@ -1305,14 +1297,53 @@ bool TypeSystemParser::applyCommonAttributes(const ConditionalStreamReader &read
         const auto name = attributes->at(i).qualifiedName();
         if (name ==  u"revision") {
             type->setRevision(attributes->takeAt(i).value().toInt());
+        }
+    }
+    return true;
+}
+
+bool TypeSystemParser::applyCppAttributes(const ConditionalStreamReader &reader,
+                                          const CppTypeEntryPtr &type,
+                                          QXmlStreamAttributes *attributes)
+{
+    if (!applyCommonAttributes(reader, type, attributes))
+        return false;
+    for (auto i = attributes->size() - 1; i >= 0; --i) {
+        const auto name = attributes->at(i).qualifiedName();
+        if (name == u"default-constructor") {
+            type->setDefaultConstructor(attributes->takeAt(i).value().toString());
         } else if (name == u"view-on") {
             const QString name = attributes->takeAt(i).value().toString();
-            TypeEntryPtr views = findViewedType(name);
+            auto views = findViewedType(name);
             if (!views) {
                 m_error = msgCannotFindView(name, type->name());
                 return false;
             }
             type->setViewOn(views);
+        } else if (name == defaultConstructibleAttribute) {
+            const bool v = convertBoolean(attributes->takeAt(i).value(),
+                                          defaultConstructibleAttribute, false);
+            type->setDefaultConstructibleFlag(v ? TypeSystem::DefaultConstructibleFlag::Enabled
+                                                : TypeSystem::DefaultConstructibleFlag::Disabled);
+        } else if (name == copyableAttribute) {
+            const bool v = convertBoolean(attributes->takeAt(i).value(),
+                                          copyableAttribute, false);
+            type->setCopyableFlag(v ? TypeSystem::CopyableFlag::Enabled
+                                    : TypeSystem::CopyableFlag::Disabled);
+        } else if (name == movableAttribute) {
+            const bool v = convertBoolean(attributes->takeAt(i).value(),
+                                          movableAttribute, false);
+            type->setMovableFlag(v ? TypeSystem::MovableFlag::Enabled
+                                   : TypeSystem::MovableFlag::Disabled);
+        } else if (name == qtMetaTypeAttribute) {
+            const auto attribute = attributes->takeAt(i);
+            const auto qtMetaTypeOpt = qtMetaTypeFromAttribute(attribute.value());
+            if (qtMetaTypeOpt.has_value()) {
+                type->setQtMetaTypeRegistration(qtMetaTypeOpt.value());
+            } else {
+                qCWarning(lcShiboken, "%s",
+                          qPrintable(msgInvalidAttributeValue(attribute)));
+            }
         }
     }
     return true;
@@ -1356,7 +1387,7 @@ FlagsTypeEntryPtr
     }
 
     ftype->setOriginalName(flagName);
-    if (!applyCommonAttributes(reader, ftype, attributes))
+    if (!applyCppAttributes(reader, ftype, attributes))
         return nullptr;
 
     QStringList lst = flagName.split(u"::"_s);
@@ -1375,8 +1406,7 @@ FlagsTypeEntryPtr
     m_context->db->addFlagsType(ftype);
     m_context->db->addType(ftype);
 
-    const int revisionIndex =
-        indexOfAttribute(*attributes, u"flags-revision");
+    const auto revisionIndex = indexOfAttribute(*attributes, u"flags-revision");
     ftype->setRevision(revisionIndex != -1
                        ? attributes->takeAt(revisionIndex).value().toInt()
                        : enumEntry->revision());
@@ -1396,7 +1426,9 @@ SmartPointerTypeEntryPtr
     QString valueCheckMethod;
     QString nullCheckMethod;
     QString resetMethod;
+    TypeDatabaseParserContext::SmartPointerEntry entry;
     QString instantiations;
+    QString excludedInstantiations;
     for (auto i = attributes->size() - 1; i >= 0; --i) {
         const auto name = attributes->at(i).qualifiedName();
         if (name == u"type") {
@@ -1412,7 +1444,9 @@ SmartPointerTypeEntryPtr
         } else if (name == u"ref-count-method") {
             refCountMethodName = attributes->takeAt(i).value().toString();
         } else if (name == u"instantiations") {
-            instantiations = attributes->takeAt(i).value().toString();
+            entry.instantiations = attributes->takeAt(i).value().toString();
+        } else if (name == u"excluded-instantiations") {
+            entry.excludedInstantiations = attributes->takeAt(i).value().toString();
         } else if (name == u"value-check-method") {
             valueCheckMethod = attributes->takeAt(i).value().toString();
         } else if (name == u"null-check-method") {
@@ -1449,13 +1483,13 @@ SmartPointerTypeEntryPtr
     auto type = std::make_shared<SmartPointerTypeEntry>(name, getter, smartPointerType,
                                                         refCountMethodName, since,
                                                         currentParentTypeEntry());
-    if (!applyCommonAttributes(reader, type, attributes))
+    if (!applyComplexTypeAttributes(reader, type, attributes))
         return nullptr;
-    applyComplexTypeAttributes(reader, type, attributes);
     type->setNullCheckMethod(nullCheckMethod);
     type->setValueCheckMethod(valueCheckMethod);
     type->setResetMethod(resetMethod);
-    m_context->smartPointerInstantiations.insert(type, instantiations);
+    if (!entry.instantiations.isEmpty() || !entry.excludedInstantiations.isEmpty())
+        m_context->smartPointerInstantiations.insert(type, entry);
     return type;
 }
 
@@ -1468,7 +1502,7 @@ PrimitiveTypeEntryPtr
         return nullptr;
     auto type = std::make_shared<PrimitiveTypeEntry>(name, since, currentParentTypeEntry());
     QString targetLangApiName;
-    if (!applyCommonAttributes(reader, type, attributes))
+    if (!applyCppAttributes(reader, type, attributes))
         return nullptr;
     for (auto i = attributes->size() - 1; i >= 0; --i) {
         const auto name = attributes->at(i).qualifiedName();
@@ -1483,8 +1517,6 @@ PrimitiveTypeEntryPtr
             const bool v = convertBoolean(attributes->takeAt(i).value(),
                                           preferredTargetLangTypeAttribute, true);
             type->setPreferredTargetLangType(v);
-        } else if (name == u"default-constructor") {
-             type->setDefaultConstructor(attributes->takeAt(i).value().toString());
         }
     }
 
@@ -1549,9 +1581,8 @@ ContainerTypeEntryPtr
     attributes->removeAt(typeIndex);
     auto type = std::make_shared<ContainerTypeEntry>(name, containerTypeOpt.value(),
                                                      since, currentParentTypeEntry());
-    if (!applyCommonAttributes(reader, type, attributes))
+    if (!applyComplexTypeAttributes(reader, type, attributes))
         return nullptr;
-    applyComplexTypeAttributes(reader, type, attributes);
 
     for (auto i = attributes->size() - 1; i >= 0; --i) {
         const auto name = attributes->at(i).qualifiedName();
@@ -1597,7 +1628,8 @@ EnumTypeEntryPtr
     if (!checkRootElement())
         return nullptr;
     auto entry = std::make_shared<EnumTypeEntry>(name, since, currentParentTypeEntry());
-    applyCommonAttributes(reader, entry, attributes);
+    if (!applyCppAttributes(reader, entry, attributes))
+        return nullptr;
     entry->setTargetLangPackage(m_defaultPackage);
 
     QString flagNames;
@@ -1652,7 +1684,6 @@ NamespaceTypeEntryPtr
         return nullptr;
     auto result = std::make_shared<NamespaceTypeEntry>(name, since, currentParentTypeEntry());
     auto visibility = TypeSystem::Visibility::Unspecified;
-    applyCommonAttributes(reader, result, attributes);
     for (auto i = attributes->size() - 1; i >= 0; --i) {
         const auto attributeName = attributes->at(i).qualifiedName();
         if (attributeName == u"files") {
@@ -1696,7 +1727,8 @@ NamespaceTypeEntryPtr
     if (visibility != TypeSystem::Visibility::Unspecified)
         result->setVisibility(visibility);
     // Handle legacy "generate" before the common handling
-    applyComplexTypeAttributes(reader, result, attributes);
+    if (!applyComplexTypeAttributes(reader, result, attributes))
+        return {};
 
     if (result->extends() && !result->hasPattern()) {
         m_error = msgExtendingNamespaceRequiresPattern(name);
@@ -1714,13 +1746,8 @@ ValueTypeEntryPtr
     if (!checkRootElement())
         return nullptr;
     auto typeEntry = std::make_shared<ValueTypeEntry>(name, since, currentParentTypeEntry());
-    if (!applyCommonAttributes(reader, typeEntry, attributes))
+    if (!applyComplexTypeAttributes(reader, typeEntry, attributes))
         return nullptr;
-    applyComplexTypeAttributes(reader, typeEntry, attributes);
-    const int defaultCtIndex =
-        indexOfAttribute(*attributes, u"default-constructor");
-    if (defaultCtIndex != -1)
-         typeEntry->setDefaultConstructor(attributes->takeAt(defaultCtIndex).value().toString());
     return typeEntry;
 }
 
@@ -1802,18 +1829,18 @@ TypedefEntryPtr
     const QString sourceType = attributes->takeAt(sourceIndex).value().toString();
     auto result = std::make_shared<TypedefEntry>(name, sourceType, since,
                                                  currentParentTypeEntry());
-    if (!applyCommonAttributes(reader, result, attributes))
+    if (!applyComplexTypeAttributes(reader, result, attributes))
         return nullptr;
-    applyComplexTypeAttributes(reader, result, attributes);
     return result;
 }
 
-void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader &reader,
-                                         const ComplexTypeEntryPtr &ctype,
-                                         QXmlStreamAttributes *attributes) const
+bool TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader &reader,
+                                                  const ComplexTypeEntryPtr &ctype,
+                                                  QXmlStreamAttributes *attributes)
 {
+    if (!applyCppAttributes(reader, ctype, attributes))
+        return false;
     bool generate = true;
-    ctype->setCopyable(ComplexTypeEntry::Unknown);
     auto exceptionHandling = m_exceptionHandling;
     auto allowThread = m_allowThread;
 
@@ -1847,10 +1874,6 @@ void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader
             ctype->setPolymorphicNameFunction(attributes->takeAt(i).value().toString());
         } else if (name == u"polymorphic-id-expression") {
             ctype->setPolymorphicIdValue(attributes->takeAt(i).value().toString());
-        } else if (name == copyableAttribute) {
-            const bool v = convertBoolean(attributes->takeAt(i).value(),
-                                          copyableAttribute, false);
-            ctype->setCopyable(v ? ComplexTypeEntry::CopyableSet : ComplexTypeEntry::NonCopyableSet);
         } else if (name == exceptionHandlingAttribute) {
             const auto attribute = attributes->takeAt(i);
             const auto exceptionOpt = exceptionHandlingFromAttribute(attribute.value());
@@ -1928,15 +1951,6 @@ void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader
                 qCWarning(lcShiboken, "%s",
                           qPrintable(msgInvalidAttributeValue(attribute)));
             }
-        } else if (name == qtMetaTypeAttribute) {
-            const auto attribute = attributes->takeAt(i);
-            const auto qtMetaTypeOpt = qtMetaTypeFromAttribute(attribute.value());
-            if (qtMetaTypeOpt.has_value()) {
-                ctype->setQtMetaTypeRegistration(qtMetaTypeOpt.value());
-            } else {
-                qCWarning(lcShiboken, "%s",
-                          qPrintable(msgInvalidAttributeValue(attribute)));
-            }
         } else if (name == parentManagementAttribute) {
             const auto attribute = attributes->takeAt(i);
             if (convertBoolean(attribute.value(), parentManagementAttribute, false))
@@ -1960,6 +1974,7 @@ void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader
         ctype->setCodeGeneration(m_generate);
     else
         ctype->setCodeGeneration(TypeEntry::GenerationDisabled);
+    return true;
 }
 
 bool TypeSystemParser::parseConfiguration(StackElement topElement,
@@ -1988,6 +2003,30 @@ bool TypeSystemParser::parseConfiguration(StackElement topElement,
     return true;
 }
 
+bool TypeSystemParser::parseOverloadRemoval(StackElement topElement,
+                                            QXmlStreamAttributes *attributes)
+{
+    if (topElement != StackElement::Root) {
+        m_error = u"<overload-removal> can only appear under the root element."_s;
+        return false;
+    }
+    OverloadRemovalRule rule;
+    for (auto i = attributes->size() - 1; i >= 0; --i) {
+        const auto name = attributes->at(i).qualifiedName();
+        if (name == u"type") {
+            rule.type = attributes->takeAt(i).value().toString();
+        } else if (name == u"replaces") {
+            rule.redundantTypes = attributes->takeAt(i).value().toString().split(u';');
+        }
+    }
+    if (rule.type.isEmpty() || rule.redundantTypes.isEmpty()) {
+        m_error = u"<overload-removal> requires \"type\" and \"replaces\" attributes."_s;
+        return false;
+    }
+    TypeDatabase::instance()->addOverloadRemovalRule(rule);
+    return true;
+}
+
 bool TypeSystemParser::parseRenameFunction(const ConditionalStreamReader &,
                                   QString *name, QXmlStreamAttributes *attributes)
 {
@@ -2051,6 +2090,8 @@ bool TypeSystemParser::parseInjectDocumentation(const ConditionalStreamReader &,
     TypeSystem::DocModificationMode mode = TypeSystem::DocModificationReplace;
     DocumentationFormat format = DocumentationFormat::Native;
     DocumentationEmphasis emphasis = DocumentationEmphasis::None;
+    DocumentationTarget target = DocumentationTarget::Documentation;
+
     for (auto i = attributes->size() - 1; i >= 0; --i) {
         const auto name = attributes->at(i).qualifiedName();
         if (name == u"mode") {
@@ -2077,6 +2118,14 @@ bool TypeSystemParser::parseInjectDocumentation(const ConditionalStreamReader &,
                 return false;
             }
             emphasis = emphasisOpt.value();
+        } else if (name == u"target") {
+            const auto attribute = attributes->takeAt(i);
+            const auto targetOpt = docTargetFromAttribute(attribute.value());
+            if (!targetOpt.has_value()) {
+                m_error = msgInvalidAttributeValue(attribute);
+                return false;
+            }
+            target = targetOpt.value();
         }
     }
 
@@ -2085,10 +2134,16 @@ bool TypeSystemParser::parseInjectDocumentation(const ConditionalStreamReader &,
         return false;
     }
 
+    if (target == DocumentationTarget::DocString && mode != TypeSystem::DocModificationReplace) {
+        m_error = "Doc strings only support \"replace\""_L1;
+        return false;
+    }
+
     QString signature = isTypeEntry(topElement) ? QString() : m_currentSignature;
     DocModification mod(mode, signature);
     mod.setFormat(format);
     mod.setEmphasis(emphasis);
+    mod.setTarget(target);
     if (hasFileSnippetAttributes(attributes)) {
         const auto snippetOptional = readFileSnippet(attributes);
         if (!snippetOptional.has_value())
@@ -2203,8 +2258,8 @@ TypeSystemTypeEntryPtr TypeSystemParser::parseRootElement(const ConditionalStrea
         std::const_pointer_cast<TypeSystemTypeEntry>(m_context->db->findTypeSystemType(m_defaultPackage));
     const bool add = !moduleEntry;
     if (add) {
-        moduleEntry.reset(new TypeSystemTypeEntry(m_defaultPackage, since,
-                                                  currentParentTypeEntry()));
+        moduleEntry = std::make_shared<TypeSystemTypeEntry>(m_defaultPackage, since,
+                                                            currentParentTypeEntry());
         moduleEntry->setSubModule(subModuleOf);
     }
     if (!docPackage.isEmpty())
@@ -2344,17 +2399,9 @@ bool TypeSystemParser::parseCustomConversion(const ConditionalStreamReader &,
             if (lang != TypeSystem::TargetLangCode)
                 return true;
 
-            QFile conversionSource(sourceFile);
-            if (!conversionSource.open(QIODevice::ReadOnly | QIODevice::Text)) {
-                m_error = msgCannotOpenForReading(conversionSource);
-                return false;
-            }
-            const auto conversionRuleOptional =
-                extractSnippet(QString::fromUtf8(conversionSource.readAll()), snippetLabel);
-            if (!conversionRuleOptional.has_value()) {
-                m_error = msgCannotFindSnippet(sourceFile, snippetLabel);
+            const auto conversionRuleOptional = readFileSnippetContents(sourceFile, snippetLabel);
+            if (!conversionRuleOptional.has_value())
                 return false;
-            }
             valueTypeEntry->setTargetConversionRule(conversionRuleOptional.value());
         }
         return true;
@@ -2484,7 +2531,7 @@ bool TypeSystemParser::parseModifyArgument(const ConditionalStreamReader &,
         return false;
     }
 
-    int idx;
+    int idx = 0;
     if (!parseArgumentIndex(index, &idx, &m_error))
         return false;
 
@@ -2507,8 +2554,7 @@ bool TypeSystemParser::parseNoNullPointer(const ConditionalStreamReader &reader,
     ArgumentModification &lastArgMod = m_contextStack.top()->functionMods.last().argument_mods().last();
     lastArgMod.setNoNullPointers(true);
 
-    const int defaultValueIndex =
-        indexOfAttribute(*attributes, u"default-value");
+    const auto defaultValueIndex = indexOfAttribute(*attributes, u"default-value");
     if (defaultValueIndex != -1) {
         const QXmlStreamAttribute attribute = attributes->takeAt(defaultValueIndex);
         qCWarning(lcShiboken, "%s",
@@ -3041,6 +3087,20 @@ bool TypeSystemParser::parseParentOwner(const ConditionalStreamReader &,
     return true;
 }
 
+std::optional<QString>
+    TypeSystemParser::readFileSnippetContents(const QString &fileName,
+                                              const QString &snippetName)
+{
+    static FileCache cache;
+
+    const auto result = snippetName.isEmpty() ? cache.fileContents(fileName)
+        : cache.fileSnippet(fileName, snippetName, snippetPattern(snippetName));
+
+    if (!result.has_value())
+        m_error = cache.errorString();
+    return result;
+}
+
 std::optional<TypeSystemParser::Snippet>
     TypeSystemParser::readFileSnippet(QXmlStreamAttributes *attributes)
 {
@@ -3059,24 +3119,10 @@ std::optional<TypeSystemParser::Snippet>
     }
     const QString resolved = m_context->db->modifiedTypesystemFilepath(result.fileName,
                                                                        m_currentPath);
-    if (!QFile::exists(resolved)) {
-        m_error = u"File for inject code not exist: "_s
-                  + QDir::toNativeSeparators(result.fileName);
+    auto snippetO = readFileSnippetContents(resolved, result.snippetLabel);
+    if (!snippetO.has_value())
         return std::nullopt;
-    }
-    QFile codeFile(resolved);
-    if (!codeFile.open(QIODevice::Text | QIODevice::ReadOnly)) {
-        m_error = msgCannotOpenForReading(codeFile);
-        return std::nullopt;
-    }
-    const auto contentOptional = extractSnippet(QString::fromUtf8(codeFile.readAll()),
-                                                result.snippetLabel);
-    codeFile.close();
-    if (!contentOptional.has_value()) {
-        m_error = msgCannotFindSnippet(resolved, result.snippetLabel);
-        return std::nullopt;
-    }
-    result.content = contentOptional.value();
+    result.content = snippetO.value();
     return result;
 }
 
@@ -3212,7 +3258,28 @@ bool TypeSystemParser::parseSystemInclude(const ConditionalStreamReader &,
     return true;
 }
 
-TemplateInstance *
+TemplateEntryPtr TypeSystemParser::parseTemplate(QXmlStreamAttributes *attributes)
+{
+    auto result = std::make_shared<TemplateEntry>();
+    if (hasFileSnippetAttributes(attributes)) {
+        const auto snippetOptional = readFileSnippet(attributes);
+        if (!snippetOptional.has_value())
+            return {};
+        result->addCode(snippetOptional.value().content);
+    }
+    for (auto i = attributes->size() - 1; i >= 0; --i) {
+        const auto name = attributes->at(i).qualifiedName();
+        if (name == nameAttribute)
+            result->setName(attributes->takeAt(i).value().toString());
+    }
+    if (result->name().isEmpty()) {
+        m_error = msgMissingAttribute(nameAttribute);
+        return {};
+    }
+    return result;
+}
+
+std::optional<TemplateInstance>
     TypeSystemParser::parseInsertTemplate(const ConditionalStreamReader &,
                                           StackElement topElement,
                                           QXmlStreamAttributes *attributes)
@@ -3224,14 +3291,14 @@ TemplateInstance *
         (topElement != StackElement::ConversionRule)) {
         m_error = u"Can only insert templates into code snippets, templates, "\
                    "conversion-rule, native-to-target or add-conversion tags."_s;
-        return nullptr;
+        return std::nullopt;
     }
     const auto nameIndex = indexOfAttribute(*attributes, nameAttribute);
     if (nameIndex == -1) {
         m_error = msgMissingAttribute(nameAttribute);
-        return nullptr;
+        return std::nullopt;
     }
-    return new TemplateInstance(attributes->takeAt(nameIndex).value().toString());
+    return TemplateInstance(attributes->takeAt(nameIndex).value().toString());
 }
 
 bool TypeSystemParser::parseReplace(const ConditionalStreamReader &,
@@ -3465,8 +3532,8 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack
                 return false;
             auto ce = std::make_shared<ObjectTypeEntry>(name, versionRange.since, currentParentTypeEntry());
             top->entry = ce;
-            applyCommonAttributes(reader, top->entry, &attributes);
-            applyComplexTypeAttributes(reader, ce, &attributes);
+            if (!applyComplexTypeAttributes(reader, ce, &attributes))
+                return false;
         }
             break;
         case StackElement::FunctionTypeEntry:
@@ -3512,7 +3579,8 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack
                         || element == StackElement::AddFunction
                         || element == StackElement::DeclareFunction
                         || element == StackElement::Template
-                        || element == StackElement::OpaqueContainer;
+                        || element == StackElement::OpaqueContainer
+                        || element == StackElement::OverloadRemoval;
 
         if (!topLevel && m_stack.at(m_stack.size() - 2) == StackElement::Root) {
             m_error = u"Tag requires parent: '"_s + tagName.toString() + u'\'';
@@ -3669,18 +3737,14 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack
             if (!parseSystemInclude(reader, &attributes))
                 return false;
             break;
-        case StackElement::Template: {
-            const auto nameIndex = indexOfAttribute(attributes, nameAttribute);
-            if (nameIndex == -1) {
-                m_error = msgMissingAttribute(nameAttribute);
+        case StackElement::Template:
+            m_templateEntry = parseTemplate(&attributes);
+            if (m_templateEntry == nullptr)
                 return false;
-            }
-            m_templateEntry.reset(new TemplateEntry(attributes.takeAt(nameIndex).value().toString()));
-        }
             break;
         case StackElement::InsertTemplate:
-            m_templateInstance.reset(parseInsertTemplate(reader, topElement, &attributes));
-            if (!m_templateInstance)
+            m_templateInstance = parseInsertTemplate(reader, topElement, &attributes);
+            if (!m_templateInstance.has_value())
                 return false;
             break;
         case StackElement::Replace:
@@ -3693,6 +3757,10 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack
             if (!parseConfiguration(topElement, &attributes))
                 return false;
             break;
+        case StackElement::OverloadRemoval:
+            if (!parseOverloadRemoval(topElement, &attributes))
+                return false;
+            break;
         default:
             break; // nada
         }
index 4d9d4fd926209c2e80d902edea2ce0ba9e3d129a..b5f77bebfe54a2a51b3ae36134580b7b2f44dade 100644 (file)
@@ -10,9 +10,9 @@
 #include "typesystem_typedefs.h"
 #include "codesnip.h"
 
-#include <QtCore/QStack>
-#include <QtCore/QHash>
-#include <QtCore/QScopedPointer>
+#include <QtCore/qstack.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qscopedpointer.h>
 
 #include <memory>
 #include <optional>
@@ -106,6 +106,7 @@ enum class StackElement {
             ImportFile,
             OpaqueContainer,
             Configuration,
+            OverloadRemoval,
             Unimplemented
 };
 
@@ -172,6 +173,9 @@ private:
     bool applyCommonAttributes(const ConditionalStreamReader &reader,
                                const TypeEntryPtr &type,
                                QXmlStreamAttributes *attributes);
+    bool applyCppAttributes(const ConditionalStreamReader &reader,
+                            const CppTypeEntryPtr &type,
+                            QXmlStreamAttributes *attributes);
     PrimitiveTypeEntryPtr
         parsePrimitiveTypeEntry(const ConditionalStreamReader &, const QString &name,
                                 const QVersionNumber &since, QXmlStreamAttributes *);
@@ -206,10 +210,11 @@ private:
         parseTypedefEntry(const ConditionalStreamReader &, const QString &name,
                           StackElement topElement,
                           const QVersionNumber &since, QXmlStreamAttributes *);
-    void applyComplexTypeAttributes(const ConditionalStreamReader &, const ComplexTypeEntryPtr &ctype,
-                                    QXmlStreamAttributes *) const;
+    bool applyComplexTypeAttributes(const ConditionalStreamReader &, const ComplexTypeEntryPtr &ctype,
+                                    QXmlStreamAttributes *);
     bool parseConfiguration(StackElement topElement,
                             QXmlStreamAttributes *attributes);
+    bool parseOverloadRemoval(StackElement topElement, QXmlStreamAttributes *attributes);
     bool parseRenameFunction(const ConditionalStreamReader &, QString *name,
                              QXmlStreamAttributes *);
     bool parseInjectDocumentation(const ConditionalStreamReader &, StackElement topElement,
@@ -256,15 +261,18 @@ private:
                               QXmlStreamAttributes *);
      bool parseParentOwner(const ConditionalStreamReader &, StackElement topElement,
                            QXmlStreamAttributes *);
+     std::optional<QString>
+         readFileSnippetContents(const QString &fileName, const QString &snippetName);
      std::optional<Snippet> readFileSnippet(QXmlStreamAttributes *attributes);
      bool readCodeSnippet(QXmlStreamAttributes *attributes, CodeSnip *snip);
      bool parseInjectCode(const ConditionalStreamReader &, StackElement topElement, QXmlStreamAttributes *);
      bool parseInclude(const ConditionalStreamReader &, StackElement topElement,
                        const TypeEntryPtr &entry, QXmlStreamAttributes *);
      bool parseSystemInclude(const ConditionalStreamReader &, QXmlStreamAttributes *);
-     TemplateInstance
-         *parseInsertTemplate(const ConditionalStreamReader &, StackElement topElement,
-                              QXmlStreamAttributes *);
+     TemplateEntryPtr parseTemplate(QXmlStreamAttributes *attributes);
+     std::optional<TemplateInstance>
+         parseInsertTemplate(const ConditionalStreamReader &, StackElement topElement,
+                             QXmlStreamAttributes *);
      bool parseReplace(const ConditionalStreamReader &, StackElement topElement,
                        QXmlStreamAttributes *);
      bool checkDuplicatedTypeEntry(const ConditionalStreamReader &reader,
@@ -284,7 +292,7 @@ private:
     const TypeEntry::CodeGeneration m_generate;
 
     EnumTypeEntryPtr m_currentEnum;
-    TemplateInstancePtr m_templateInstance;
+    std::optional<TemplateInstance> m_templateInstance;
     TemplateEntryPtr m_templateEntry;
     ContextStack m_contextStack;
 
index 372c7c01f6532afdfc8df5ea2b1606e23a1e741a..e0233544715a5965d67a5e990562df456dc1c7a3 100644 (file)
@@ -4,9 +4,9 @@
 #ifndef VOIDTYPEENTRY_H
 #define VOIDTYPEENTRY_H
 
-#include "typesystem.h"
+#include "cpptypeentry.h"
 
-class VoidTypeEntry : public TypeEntry
+class VoidTypeEntry : public CppTypeEntry
 {
 public:
     VoidTypeEntry();
@@ -14,7 +14,7 @@ public:
     TypeEntry *clone() const override;
 
 protected:
-    explicit VoidTypeEntry(TypeEntryPrivate *d);
+    explicit VoidTypeEntry(CppTypeEntryPrivate *d);
 };
 
 #endif // VOIDTYPEENTRY_H
index ac23c9c9c985f390442d2cda3305dd857d5fe77d..b642421940e09ad6fe12b069642f3c7864cd5eb7 100644 (file)
@@ -3,7 +3,7 @@
 #ifndef XMLUTILS_H
 #define XMLUTILS_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 #include <memory>
 
index 5a9a26913fd7f445c6976da02d37cadca9e0c631..26ad1685bcafb33235fcb0828561bac69a01b5da 100644 (file)
@@ -6,11 +6,11 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QByteArray>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QString>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qstring.h>
 
 #include <libxslt/xsltutils.h>
 #include <libxslt/transform.h>
index 0dd8eafcbde5d2db759efda72a230793c2dd82ac..353070d4bc9bbf8fe74df8ffa33eca9186c97ce2 100644 (file)
@@ -3,7 +3,7 @@
 #ifndef XMLUTILS_LIBXSLT_H
 #define XMLUTILS_LIBXSLT_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 #include <memory>
 
index 2748270449f8cb7f1f294f000d71a7a73433f19e..ef4d276a271fcc97e03961a2ec8f4644a4a92e57 100644 (file)
@@ -3,7 +3,7 @@
 #ifndef XMLUTILS_QT_H
 #define XMLUTILS_QT_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 #include <memory>
 
index 9c477705bc9ab76fa4939e95a9a32e45352f7e61..bcaf31e89ee1821fd6c65a27e54b65e8edb29014 100644 (file)
@@ -358,9 +358,9 @@ macro(shiboken_find_required_python)
 endmacro()
 
 macro(shiboken_validate_python_version)
-    if(Python_VERSION_MAJOR EQUAL "3" AND Python_VERSION_MINOR LESS "8")
+    if(Python_VERSION_MAJOR EQUAL "3" AND Python_VERSION_MINOR LESS "9")
             message(FATAL_ERROR
-                   "Shiboken requires Python 3.8+.")
+                   "Shiboken requires Python 3.9+.")
     endif()
 endmacro()
 
index 68f96dded11e7804ef32435118b0d9ebd1bee9f3..de031b96bb4610d984a50992cd5967f08404d561 100644 (file)
@@ -67,7 +67,7 @@ Documentation
     .. grid-item-card::
         :class-item: text-center
 
-        Reference and functionallities.
+        Reference and functionality.
         +++
         .. button-ref:: typesystem
             :color: primary
index 88befaa2d61ae9c5c407b1783d63414deabfe95a..6e19f1a5e6164419660e1541099fc069676e9740 100644 (file)
@@ -8,7 +8,7 @@ import sys
 from argparse import ArgumentParser, RawTextHelpFormatter
 
 
-DESC="""Qhp file updater
+DESC = """Qhp file updater
 
 Replaces virtual folder ids in .qhp files preparing for
 registering the documentation in Qt Assistant."""
@@ -49,7 +49,7 @@ if __name__ == '__main__':
                             help='String to be injected into the Qhp file.')
     arg_parser.add_argument("--pyside", "-p", action="store_true",
                             help="Strip the PySide module path off the index entries.")
-    arg_parser.add_argument("file", type=str,  help='Qhp filename.')
+    arg_parser.add_argument("file", type=str, help='Qhp filename.')
     options = arg_parser.parse_args()
     virtual_folder = options.vfolder
     strip_pyside_module = options.pyside
index 37a4a661b794c8c9e28fc2eca9636bc0075f1cce..07b5792ac0d5d165cd6d6c73f560caf6f295120c 100644 (file)
@@ -39,6 +39,7 @@ Modifying types
    :maxdepth: 1
 
    Function argument modifications <typesystem_arguments.rst>
+   typesystem_overloads.rst
    typesystem_codeinjection.rst
    typesystem_converters.rst
    typesystem_containers.rst
@@ -55,6 +56,7 @@ Object ownership
    :maxdepth: 1
 
    typesystem_ownership.rst
+   typesystem_objectvalue.rst
 
 Extra options and Python caveats
 --------------------------------
index c3ad1c7077befafcbcd6a57af4641819e78cb374..90387ec1a2379862f94aae63e8db3543c845804d 100644 (file)
@@ -6,9 +6,10 @@ modify-argument
 Function argument modifications consist of a list of ``modify-argument`` nodes
 contained in :ref:`modify-function`, :ref:`add-function` or
 :ref:`declare-function` nodes. Nested :ref:`remove-argument`,
-:ref:`replace-default-expression`, :ref:`remove-default-expression`,
-:ref:`replace-type`, :ref:`reference-count` and :ref:`define-ownership`
-nodes specify the details of the modification.
+:ref:`rename-to`, :ref:`remove-default-expression`, :ref:`replace-default-expression`,
+:ref:`replace-type`, :ref:`define-ownership`, :ref:`parent-on-arguments`,
+:ref:`reference-count`, :ref:`conversionrule-on-arguments` and
+and :ref:`replace-value` nodes specify the details of the modification.
 
 .. code-block:: xml
 
@@ -194,7 +195,7 @@ counting, since the model should be kept alive as long as the view lives.
 Remember that our hypothetical view cannot become a :ref:`parent` of the
 model, since the said model could be used by other views as well.
 
-.. _parent:
+.. _parent-on-arguments:
 
 parent
 ^^^^^^
@@ -236,7 +237,8 @@ It is then a child of the :ref:`modify-argument` node:
     </modify-argument>
 
 The ``class`` attribute accepts one of the following values to define the
-conversion direction to be either ``target-to-native`` or ``native-to-target``:
+conversion direction to be either ``target-to-native`` (Python to C++)
+or ``native-to-target`` (C++ to Python):
 
 * ``native``: Defines the conversion direction to be ``target-to-native``.
               It is similar to the existing ``<target-to-native>`` element.
@@ -246,10 +248,28 @@ conversion direction to be either ``target-to-native`` or ``native-to-target``:
               It is similar to the existing ``<native-to-target>`` element.
               See :ref:`Conversion Rule Tag <conversion-rule-tag>` for more information.
 
+The conversions appear in the generated code (see :ref:`codegenerationterminology`)
+as follows:
+
++--------------+----------------+-------------+
+|              | Python Wrapper | C++ Wrapper |
++--------------+----------------+-------------+
+|Parameters    | ``native``     | ``target``  |
++--------------+----------------+-------------+
+|Return value  | ``target``     | ``native``  |
++--------------+----------------+-------------+
+
 This node is typically used in combination with the :ref:`replace-type` and
 :ref:`remove-argument` nodes. The given code is used instead of the generator's
 conversion code.
 
+An example for removing an argument might be a C++ function accepting a C-style
+array and a length parameter, which is modified to receive a Python list
+instead. The array parameter's type would be modified to ``PySequence`` with
+``native`` conversion rule; and the length parameter would be removed,
+specifying a native ``native`` conversion rule which would determine the value
+from the size of the Python sequence passed.
+
 Writing %N in the code (where N is a number), will insert the name of the
 nth argument. Alternatively, %in and %out which will be replaced with the
 name of the conversion's input and output variable, respectively. Note the
@@ -261,6 +281,10 @@ output variable must be declared explicitly, for example:
     bool %out = (bool) %in;
     </conversion-rule>
 
+.. note::
+    Conversion rules for the `C++ Wrapper` only need to be specified if the
+    function is virtual and overidable.
+
 .. note::
 
     You can also use the ``conversion-rule`` node to specify
index 03d5f4b16c5ae3db71cca212fecd11ef78377597..0e047f38fc57ef380e74c5a7a2c210cb489913a9 100644 (file)
@@ -174,7 +174,7 @@ In other words, use
 .. code-block:: xml
 
      <inject-code class="target" position="beginning | end">
-         %CPPSELF.originalMethodName();
+         %CPPSELF.%FUNCTION_NAME();
      </inject-code>
 
 
@@ -184,7 +184,7 @@ instead of
 .. code-block:: xml
 
      <inject-code class="target" position="beginning | end">
-        %CPPSELF.%FUNCTION_NAME();
+        %CPPSELF.originalMethodName();
      </inject-code>
 
 
index ab6fba93099394e78a5eda94af78c79c9148ca3e..f34f5b829aeef341b19a959808400e798c356344 100644 (file)
@@ -16,7 +16,8 @@ C++ and vice-versa.
 
     // C++ class
     struct Complex {
-        Complex(double real, double imag);
+        explicit Complex(double real, double imag);
+
         double real() const;
         double imag() const;
     };
@@ -82,8 +83,9 @@ Here's how to do it:
 
   <!-- Code injection at module level. -->
   <inject-code class="native" position="beginning">
-  static bool Check2TupleOfNumbers(PyObject* pyIn) {
-      if (!PySequence_Check(pyIn) || !(PySequence_Size(pyIn) == 2))
+  static bool Check2TupleOfNumbers(PyObject *pyIn)
+  {
+      if (PySequence_Check(pyIn) == 0 || PySequence_Size(pyIn) != 2)
           return false;
       Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0));
       if (!PyNumber_Check(pyReal))
@@ -134,7 +136,8 @@ Container Conversions
 
 Converters for :ref:`container-type <container-type>` are pretty much the same as for other type,
 except that they make use of the type system variables
-:ref:`%INTYPE_# <intype_n>` and :ref:`%OUTTYPE_# <outtype_n>`.
+:ref:`%INTYPE_# <intype_n>` and :ref:`%OUTTYPE_# <outtype_n>` denoting the
+template parameters.
 |project| combines the conversion code for containers with the conversion
 defined (or automatically generated) for the containers.
 
@@ -147,13 +150,12 @@ defined (or automatically generated) for the containers.
 
           <native-to-target>
           PyObject* %out = PyDict_New();
-          %INTYPE::const_iterator it = %in.begin();
-          for (; it != %in.end(); ++it) {
-            %INTYPE_0 key = it->first;
-            %INTYPE_1 value = it->second;
-                    PyDict_SetItem(%out,
+          for (auto it = %in.cbegin(), end = %in.cend(); it != end; ++it) {
+            const auto &amp;key = it->first;
+            const auto &amp;value = it->second;
+            PyDict_SetItem(%out,
                            %CONVERTTOPYTHON[%INTYPE_0](key),
-                   %CONVERTTOPYTHON[%INTYPE_1](value));
+                           %CONVERTTOPYTHON[%INTYPE_1](value));
           }
           return %out;
           </native-to-target>
@@ -161,8 +163,8 @@ defined (or automatically generated) for the containers.
           <target-to-native>
 
             <add-conversion type="PyDict">
-            PyObject* key;
-            PyObject* value;
+            PyObject *key{};
+            PyObject *value{};
             Py_ssize_t pos = 0;
             while (PyDict_Next(%in, &amp;pos, &amp;key, &amp;value)) {
                 %OUTTYPE_0 cppKey = %CONVERTTOCPP[%OUTTYPE_0](key);
@@ -183,10 +185,10 @@ defined (or automatically generated) for the containers.
           For this case, a number of pre-defined conversion templates
           are provided (see :ref:`predefined_templates`).
 
-.. _variables_and_functions:
+.. _converter_variables_and_functions:
 
-Variables & Functions
-=====================
+Converter Variables & Functions
+===============================
 
 
 .. _in:
@@ -212,7 +214,7 @@ Variables & Functions
 .. _intype_n:
 
 **%INTYPE_#**
-  Replaced by the name of the #th type used in a container.
+  Replaced by the name of the #th template parameter type used in a container.
 
 
 .. _outtype:
@@ -225,7 +227,7 @@ Variables & Functions
 .. _outtype_n:
 
 **%OUTTYPE_#**
-  Replaced by the name of the #th type used in a container.
+  Replaced by the name of the #th template parameter type used in a container.
 
 
 .. _checktype:
index 47ae54fceaa5e270bd513a561d74940698acc165..8786f57004b652405b8859c33d76755915c2d61d 100644 (file)
@@ -13,6 +13,7 @@ documentation. This node is a child of the :ref:`object-type`,
      <value-type>
          <inject-documentation mode="append | prepend | replace" format="native | target"
                                emphasis="none | language-note"
+                               target = "documentation | docstring"
                                file="[file]" snippet="[label]">
              // the documentation
          </inject-code>
@@ -36,6 +37,10 @@ The optional ``file`` attribute specifies the file name
 The optional ``snippet`` attribute specifies the snippet label
 (see :ref:`external-snippets`).
 
+The optional ``target`` attribute can be used to specify that the documentation snippet
+is meant to be used as a Python doc string during code generation instead of documentation.
+``mode="replace"`` must be used for this.
+
 At the moment the only supported backend is Sphinx.
 
 If the injected documentation contains a Sphinx function directive, no
index b63a4710ab155f39f59dd80ac85b52544fad208a..2b1169a4d974631f9aa305d3a021f85a1d9e975a 100644 (file)
@@ -524,13 +524,16 @@ in case of several out-parameters):
 
     virtual int getInt() const;
 
-For the binding itself, use the common argument modifications (removing
-arguments, modifying return types with injected code snippets) to modify the
-signature.
+In most cases, this can be achieved by specifying conversion rules for
+arguments (see :ref:`conversionrule-on-arguments`, :ref:`modify-argument`)
+for the Python wrapper as well as C++ wrapper code.
 
-To make it possible to reimplement the function in Python with the modified
-signature, add a ``python-override`` function with that signature, using an
-arbitrary name for disambiguation:
+For cases where this is not possible, code implementing a Python override can
+be generated for arbitrary signatures.
+
+Taking the above example, to make it possible to reimplement the function in
+Python with the modified signature, add a ``python-override`` function with
+that signature, using an arbitrary name for disambiguation:
 
 .. code-block:: xml
 
diff --git a/sources/shiboken6/doc/typesystem_objectvalue.rst b/sources/shiboken6/doc/typesystem_objectvalue.rst
new file mode 100644 (file)
index 0000000..9b3d29f
--- /dev/null
@@ -0,0 +1,61 @@
+.. _objects-values:
+
+******************
+Objects and Values
+******************
+
+Class exposed by shiboken may be specified as either **object types**
+indicated by the :ref:`object-type` XML element, or **value types** indicated
+by :ref:`value-type` XML element.
+
+The choice should follow the usage of the class in C++.
+
+Non-copyable classes with virtual functions that form an inheritance hierarchy
+should be **object types**. They are typically passed by pointer in C++ and
+stored on the heap.
+
+Classes with value semantics should be **value types**. They are typically
+passed by value in C++, and stored on the function call stack. This is also a
+good default for the remaining classes.
+
+When passing **value types** to functions, shiboken will generate code to
+handle implicit conversions as is done in C++. For example, in Qt, it is
+possible to pass instances of **QColor** to a function taking a **QBrush**,
+since **QBrush** has a constructor accepting **QColor**.
+
+Aspects of Value Types
+======================
+
+shiboken tries to detect whether a class is default/copy constructible when
+parsing the class declaration. No detection is done to for movability; it is
+assumed to be enabled by default. These values can be overridden by attributes
+on the :ref:`value-type` XML element in case auto-detection fails. For
+non-default constructible types, it is possible to specify a default
+constructor expression to work around.
+
+shiboken assumes that copy constructible classes also have a copy assignment
+operator.
+
+Default constructibility
+------------------------
+
+Value types should be default constructible.
+
+For generating virtual functions returning value types by value, this
+is a hard requirement.
+
+For generating bindings (passing arguments), non-default constructible types
+will use pointer conversions instead of copy conversion. Implicit conversions
+will then not be generated. Otherwise, it should be equivalent for passing by
+value or const reference. When passing a non-default constructible type by
+reference to a function that actually modifies the parameter, the modification
+will affect the passed instance.
+
+Copy constructibility
+---------------------
+
+Copy constructibility/copy assignment is required by the value converters
+generated by shiboken.
+
+For move-only types, shiboken will then insert ``std::move()`` where needed.
+This will leave moved-out instances behind.
diff --git a/sources/shiboken6/doc/typesystem_overloads.rst b/sources/shiboken6/doc/typesystem_overloads.rst
new file mode 100644 (file)
index 0000000..9b05bf9
--- /dev/null
@@ -0,0 +1,38 @@
+.. _overload-removal:
+
+Restricting Function Overloads
+------------------------------
+
+Some class member functions have a number of overloads that differ in one parameter:
+
+.. code-block:: c++
+
+    class QByteArray {
+    public:
+    ...
+    static QByteArray number(int, int base = 10);
+    static QByteArray number(unsigned int, int base = 10);
+    static QByteArray number(long, int base = 10);
+    static QByteArray number(unsigned long, int base = 10);
+    static QByteArray number(long long, int base = 10);
+    static QByteArray number(unsigned long long, int base = 10);
+    ...
+
+In this case, it does not make sense to generate a binding for ``QByteArray number(int,...)``
+since it is equivalent to ``QByteArray number(long long,...)``.
+
+In the type system file, it is possible to specify a rule stating that the ``int``
+overload is to be removed when an ``long long`` overload exists by using
+the ``<overload-removal>`` element:
+
+.. code-block:: xml
+
+     <overload-removal type="long long" replaces="int"/>
+
+The ``type`` attribute specifies the preferred type and the
+``replaces`` attribute specifies a ';'-delimited list of types to be removed.
+
+.. note:: This is limited to the first 4 arguments of types that are passed by value or const-ref.
+
+.. note:: The rules are applied in the order specified. That is, a rule specifying that ``int``
+          replaces ``short`` should go before a rule rule specifying that ``long long`` replaces ``int``.
index 78a6ff4896c9d022032655f37fc82997c1af164c..c6b4de5c66845342b640b3b3b7e12ce078feb882 100644 (file)
@@ -1,7 +1,7 @@
-.. _special-functions:
+.. _special-python-functions:
 
-Special functions
------------------
+Special Python functions
+------------------------
 
 .. _sequence-protocol:
 
index ab085df8da8353824aa43bdd1a1942c5792c164f..c03d203b99d2f9b0cbc070f206925591ef46ccbc 100644 (file)
@@ -34,7 +34,7 @@ This is the root node containing all the type system information.
 It may contain :ref:`add-function`, :ref:`container-type`,
 :ref:`custom-type`, :ref:`enum-type`, :ref:`extra-includes`, :ref:`function`,
 :ref:`load-typesystem`, :ref:`namespace`, :ref:`object-type`,
-:ref:`opaque-container`,
+:ref:`opaque-container`, :ref:`overload-removal`,
 :ref:`primitive-type`, :ref:`rejection`, :ref:`smart-pointer-type`,
 :ref:`suppress-warning`, :ref:`template`, :ref:`system_include`,
 :ref:`typedef-type` or :ref:`value-type` child nodes.
@@ -366,9 +366,12 @@ values, some of which are typically obsolete.
 value-type
 ^^^^^^^^^^
 
-The ``value-type`` node indicates that the given C++ type is mapped onto the target
-language as a value type. This means that it is an object passed by value on C++,
-i.e. it is stored in the function call stack. It is a child of the :ref:`typesystem_details`
+The ``value-type`` node indicates that the given C++ type is mapped onto the
+target language as a value type (see :ref:`objects-values`). This means that it
+is an object passed by value on C++, i.e. it is stored in the function call
+stack.
+
+It is a child of the :ref:`typesystem_details`
 node or other type nodes and may contain :ref:`add-function`, :ref:`add-pymethoddef`,
 :ref:`configuration-element`, :ref:`declare-function`, :ref:`conversion-rule`,
 :ref:`enum-type`, :ref:`extra-includes`, :ref:`include-element`, :ref:`modify-function`,
@@ -379,7 +382,9 @@ node or other type nodes and may contain :ref:`add-function`, :ref:`add-pymethod
 
     <typesystem>
         <value-type name="..." since="..."
+         default-constructible="yes | no"
          copyable="yes | no"
+         movable="yes | no"
          allow-thread="..."
          disable-wrapper="yes | no"
          exception-handling="..."
@@ -397,15 +402,11 @@ node or other type nodes and may contain :ref:`add-function`, :ref:`add-pymethod
     </typesystem>
 
 The **name** attribute is the fully qualified C++ class name, such as
-"QMatrix" or "QPainterPath::Element". The **copyable** attribute is used to
-force or not specify if this type is copyable. The *optional* **hash-function**
+"QMatrix" or "QPainterPath::Element". The *optional* **hash-function**
 attribute informs the function name of a hash function for the type.
 
-The *optional* attribute **stream** specifies whether this type will be able to
-use externally defined operators, like QDataStream << and >>. If equals to **yes**,
-these operators will be called as normal methods within the current class.
-
-The *optional* **since** value is used to specify the API version of this type.
+The *optional* **default-constructible** attribute is used to override the
+built-in detection of the default constructor.
 
 The *optional* **default-constructor** specifies the minimal constructor
 call to build one instance of the value-type. This is not needed when the
@@ -414,6 +415,19 @@ Usually a code generator may guess a minimal constructor for a value-type based
 on its constructor signatures, thus **default-constructor** is used only in
 very odd cases.
 
+The *optional* **copyable** attribute is used to override the built-in
+detection of the copy constructor. This also implies the type has an assignment
+operator; which is important for the copy conversion.
+
+The *optional* **movable** attribute is used to specify whether the type has a
+move constructor. The default is "yes"; there is no auto-detection.
+
+The *optional* attribute **stream** specifies whether this type will be able to
+use externally defined operators, like QDataStream << and >>. If equals to **yes**,
+these operators will be called as normal methods within the current class.
+
+The *optional* **since** value is used to specify the API version of this type.
+
 For the *optional* **disable-wrapper** and **generate-functions**
 attributes, see :ref:`object-type`.
 
@@ -449,10 +463,12 @@ file where documentation of the class is to be found (``qdoc`` only).
 object-type
 ^^^^^^^^^^^
 
-The object-type node indicates that the given C++ type is mapped onto the target
-language as an object type. This means that it is an object passed by pointer on
-C++ and it is stored on the heap. It is a child of the :ref:`typesystem_details` node
-or other type nodes and may contain :ref:`add-function`, :ref:`add-pymethoddef`,
+The object-type node indicates that the given C++ type is mapped onto the
+target language as an object type (see :ref:`objects-values`). This means that
+it is an object passed by pointer on C++ and it is stored on the heap.
+
+It is a child of the :ref:`typesystem_details` node or other type nodes and may
+contain :ref:`add-function`, :ref:`add-pymethoddef`,
 :ref:`configuration-element`, :ref:`declare-function`, :ref:`enum-type`,
 :ref:`extra-includes`, :ref:`include-element`, :ref:`modify-function`,
 ``object-type``, :ref:`smart-pointer-type`, :ref:`typedef-type` or
@@ -485,12 +501,15 @@ or other type nodes and may contain :ref:`add-function`, :ref:`add-pymethoddef`,
          doc-file = "..." />
     </typesystem>
 
-The **name** attribute is the fully qualified C++ class name. If there is no
-C++ base class, the default-superclass attribute can be used to specify a
-superclass for the given type, in the generated target language API. The
+The **name** attribute is the fully qualified C++ class name. The
 **copyable** and **hash-function** attributes are the same as described for
 :ref:`value-type`.
 
+The *optional* **default-superclass** attribute can be used to specify a
+superclass for the given type in the generated target language API.
+This can be useful if the C++ base class is not exposed. The specified
+super class needs to be a direct base class of the class in question.
+
 The *optional* **force-abstract** attribute forces the class to be
 abstract, disabling its instantiation. The generator will normally detect
 this automatically unless the class inherits from an abstract base class
@@ -728,7 +747,8 @@ will be generated into specific modules.
             value-check-method="..."
             null-check-method="..."
             reset-method="..."
-            instantiations="..."/>
+            instantiations="..."
+            excluded-instantiations="..."/>
         </typesystem>
 
 
@@ -741,7 +761,7 @@ that can be used to check for ``nullptr``.
 The *optional* attribute **reset-method** specifies a method
 that can be used to clear the pointer.
 
-The *optional* instantiations attribute specifies a comma-separated
+The *optional* **instantiations** attribute specifies a comma-separated
 list of instantiation types. When left empty, all instantiations
 found in the code will be generated. The type name might optionally
 be followed an equal sign and the Python type name, for example
@@ -751,6 +771,11 @@ By default, the type will be in the namespace of the smart pointer,
 for example, ``std`` for ``std::shared_ptr``. Preceding
 the type name by ``::`` causes it to be in the global namespace.
 
+The *optional* **excluded-instantiations** attribute specifies a
+comma-separated list of types to be excluded from instantiating. Typically,
+this is used to exclude instantiations present in an underlying base module to
+prevent symbol clashes.
+
 The *optional* attribute **type** specifies the type:
 
 *shared*
@@ -889,8 +914,6 @@ for enclosing added functions referring to those classes within
 Other keywords can be specified using the
 :ref:`--keywords <conditional_keywords>` command line option.
 
-.. _private_types:
-
 Defining Entities
 ^^^^^^^^^^^^^^^^^
 
@@ -904,6 +927,8 @@ It is possible to define entities using a simple processing instruction:
 This allows for defining function signatures depending on platform
 in conjunction with :ref:`conditional_processing`.
 
+.. _private_types:
+
 Private Types
 ^^^^^^^^^^^^^
 
index a4ae89a6ff3059dd3adb346042c8aa114f8967df..264f603466c180f8b0486f5026dd1fa428896af3 100644 (file)
@@ -15,11 +15,16 @@ duplicate code when extending the generated code, and it is a child of the
 .. code-block:: xml
 
     <typesystem>
-        <template name="my_template">
+        <template name="my_template" file="[file]" snippet="[label]"/>
             // the code
         </template>
     </typesystem>
 
+Code may either be specified as element text (note that entities need to be
+used for special characters like '<'), or fetched from an external code snippet
+by specifying the ``file`` and ``snippet`` attributes (see
+:ref:`external-snippets`).
+
 Use the ``insert-template`` node to insert the template code (identified
 by the template's ``name`` attribute) into the generated code base.
 
index 5eb5d5abefa2eb3c23b3aeebeb4eea5f528f5a48..6dfd1f80178070353ba006a15f1385f697322926 100644 (file)
@@ -16,6 +16,8 @@ implementation specifics.
 Variables
 =========
 
+In addition to the below listed variables, there are some variables specific to type
+conversion code (see :ref:`converter_variables_and_functions`).
 
 .. _cpp_return_argument:
 
index aebe2cd5eeff96c7431699684adb354473a4432c..454e9cf7f3ac186fd1133f4a2da73691b914091d 100644 (file)
@@ -21,6 +21,7 @@ shiboken/cppgenerator.cpp shiboken/cppgenerator.h
 shiboken/cppgenerator_container.cpp
 shiboken/cppgenerator_smartpointer.cpp
 shiboken/ctypenames.h
+shiboken/overridecacheentry.h shiboken/overridecacheentry.cpp
 shiboken/generatorargument.cpp shiboken/generatorargument.h shiboken/generatorstrings.h
 shiboken/headergenerator.cpp shiboken/headergenerator.h
 shiboken/overloaddata.cpp shiboken/overloaddata.h
index 89cc9fa7790a8a91d3fe980dc61f6066c575cf11..2eb778b003bc3b281973772e77366a21acc716eb 100644 (file)
@@ -5,7 +5,7 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 using namespace Qt::StringLiterals;
 
index d518d134f4556d18a45edd17cadf77ad04cfce4f..c07b78868ced2d5f36678d8f777a25ca7992fe46 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef DEFAULTVALUE_H
 #define DEFAULTVALUE_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 QT_FORWARD_DECLARE_CLASS(QDebug);
 
index 32070fd0af623c97378d8971a10fa0f7d89b25c6..808234a7c3b026540b92f25d438580f23d42c066 100644 (file)
 
 #include "qtcompat.h"
 
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfo>
-#include <QtCore/QRegularExpression>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qregularexpression.h>
 
 using namespace Qt::StringLiterals;
 
@@ -158,22 +158,28 @@ std::shared_ptr<OptionsParser> Generator::createOptionsParser()
     return std::make_shared<GeneratorOptionsParser>(&GeneratorPrivate::m_options);
 }
 
+QString Generator::fileNameForClassHelper(const AbstractMetaClassCPtr &metaClass,
+                                          const QString &suffix,
+                                          FileNameFlags flags)
+{
+    QString fileNameBase = flags.testFlag(FileNameFlag::UnqualifiedName)
+        ? metaClass->name() : metaClass->qualifiedCppName();
+    if (!flags.testFlag(FileNameFlag::KeepCase))
+        fileNameBase = fileNameBase.toLower();
+    fileNameBase.replace(u"::"_s, u"_"_s);
+    return fileNameBase + suffix;
+}
+
 QString Generator::fileNameForContextHelper(const GeneratorContext &context,
                                             const QString &suffix,
                                             FileNameFlags flags)
 
 {
-    if (!context.forSmartPointer()) {
-        const auto metaClass = context.metaClass();
-        QString fileNameBase = flags.testFlag(FileNameFlag::UnqualifiedName)
-            ? metaClass->name() : metaClass->qualifiedCppName();
-        if (!flags.testFlag(FileNameFlag::KeepCase))
-            fileNameBase = fileNameBase.toLower();
-        fileNameBase.replace(u"::"_s, u"_"_s);
-        return fileNameBase + suffix;
-    }
+    if (!context.forSmartPointer())
+        return fileNameForClassHelper(context.metaClass(), suffix, flags);
 
-    // FIXME: PYSIDE7: Use the above code path for all types. Note the file
+    // FIXME: PYSIDE7: Use the above code path for all types and change
+    // parameter type to AbstractMetaClassCPtr. Note the file
     // names will then change to reflect the namespaces of the pointee
     // (smart/integer2).
     const AbstractMetaType &smartPointerType = context.preciseType();
@@ -696,7 +702,7 @@ QString Generator::globalScopePrefix(const GeneratorContext &classContext)
 }
 
 template<typename T>
-static QString getClassTargetFullName_(t, bool includePackageName)
+static QString getClassTargetFullName_(const T &t, bool includePackageName)
 {
     QString name = t->name();
     AbstractMetaClassCPtr context = t->enclosingClass();
index dbe702478d42b4bb4d89b93356bb6d6e57d756ca..3a1c0dc1d496999485d7a55758e164c2a30c9cb4 100644 (file)
@@ -6,7 +6,7 @@
 
 #include <abstractmetalang_typedefs.h>
 #include <typedatabase_typedefs.h>
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
 
 #include <memory>
 #include <optional>
@@ -46,6 +46,8 @@ public:
         SkipRemovedArguments     = 0x00000100,
 
         SkipDefaultValues        = 0x00000200,
+        // Implementation of call to Python override
+        PythonOverrideImplementation = 0x0400,
     };
     Q_DECLARE_FLAGS(Options, Option)
 
@@ -129,6 +131,9 @@ protected:
     static QString fileNameForContextHelper(const GeneratorContext &context,
                                             const QString &suffix,
                                             FileNameFlags flags = {});
+    static QString fileNameForClassHelper(const AbstractMetaClassCPtr &metaClass,
+                                          const QString &suffix,
+                                          FileNameFlags flags = {});
 
     /// Returns all primitive types found by APIExtractor
     static PrimitiveTypeEntryCList primitiveTypes();
index 4021704251e746e2c51c58d83c81cd14e0e11d01..0a1702c68e6cd8f5e6ea66cb68dbc82b4851bcdf 100644 (file)
@@ -4,7 +4,7 @@
 #include "generatorcontext.h"
 #include <abstractmetalang.h>
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 using namespace Qt::StringLiterals;
 
index b604d5f1a2576c0d06b06918d2a4b2102fe0bc54..80c37df7cd6c763296f61e740dcfea11acfe1a4a 100644 (file)
@@ -6,7 +6,7 @@
 
 #include <abstractmetalang_typedefs.h>
 #include <abstractmetatype.h>
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
 
 QT_FORWARD_DECLARE_CLASS(QDebug);
 
index 4fe16648bb837ce2920aad7912726e235de8cd4d..80047d7ce7d83096dbd5ee57d23a4425d2d10a33 100644 (file)
 #include <reporthandler.h>
 #include <typedatabase.h>
 
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QLibrary>
-#include <QtCore/QVariant>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qlibrary.h>
+#include <QtCore/qscopeguard.h>
+#include <QtCore/qvariant.h>
 
 #include "qtcompat.h"
 
@@ -252,8 +253,7 @@ int shibokenMain(const QStringList &argV)
     QHashSeed::setDeterministicGlobalSeed();
 
     ReportHandler::install();
-    if (ReportHandler::isDebug(ReportHandler::SparseDebug))
-        qCInfo(lcShiboken()).noquote().nospace() << appName << ' ' << argV.join(u' ');
+    ReportHandler::addGeneralMessage(msgCommandLineArguments(argV));
 
     Options options;
     options.setOptions(argV);
@@ -352,13 +352,18 @@ int shibokenMain(const QStringList &argV)
         return EXIT_FAILURE;
     }
 
+    auto logWriterFunc = [&commonOptions]() {
+        ReportHandler::writeGeneralLogFile(commonOptions.outputDirectory);
+    };
+    auto logWriter = qScopeGuard(logWriterFunc);
+
     extractor.setCppFileNames(cppFileNames);
     extractor.setTypeSystem(commonOptions.typeSystemFileName);
 
     ApiExtractorFlags apiExtractorFlags;
-    if (generators.constFirst()->usePySideExtensions())
+    if (Generator::usePySideExtensions())
         apiExtractorFlags.setFlag(ApiExtractorFlag::UsePySideExtensions);
-    if (generators.constFirst()->avoidProtectedHack())
+    if (Generator::avoidProtectedHack())
         apiExtractorFlags.setFlag(ApiExtractorFlag::AvoidProtectedHack);
     const std::optional<ApiExtractorResult> apiOpt = extractor.run(apiExtractorFlags);
 
index 95912c5ed51a48e059ae28ce57a7f536394573b1..7cd28be139d53fa0f6a28c8ab6b8db3ac1c386bd 100644 (file)
 
 #include "qtcompat.h"
 
-#include <QtCore/QTextStream>
-#include <QtCore/QFile>
-#include <QtCore/QDir>
-#include <QtCore/QJsonArray>
-#include <QtCore/QJsonDocument>
-#include <QtCore/QJsonObject>
-#include <QtCore/QOperatingSystemVersion>
-#include <QtCore/QSet>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qoperatingsystemversion.h>
+#include <QtCore/qset.h>
 
 #include <algorithm>
 #include <limits>
@@ -59,6 +59,7 @@ struct DocClassEntry
     QString name;
     QString fullName;
     QString file;
+    bool nested = false;
 };
 
 static bool classEntryLessThan(const DocClassEntry &e1, const DocClassEntry &e2)
@@ -255,7 +256,7 @@ static void readExtraDoc(const QFileInfo &fi,
         auto dot = name.lastIndexOf(u'.');
         if (dot != -1)
             name.remove(0, dot + 1);
-        docPackage->classPages.append({name, fullClassName, newFileName});
+        docPackage->classPages.append({name, fullClassName, newFileName, false});
     }
     extraTocEntries->append(fileNameToTocEntry(newFileName));
 }
@@ -468,8 +469,9 @@ void QtDocGenerator::generateClassRecursion(TextStream &s, const QString &target
 
     qCDebug(lcShibokenDoc, "Generating Documentation for %s", qPrintable(metaClass->fullName()));
 
+    const bool nested = metaClass->enclosingClass() != nullptr;
     m_packages[metaClass->package()].classPages.append({metaClass->name(), metaClass->fullName(),
-                                                        fileNameForContext(classContext)});
+                                                        fileNameForContext(classContext), nested});
 
     doGenerateClass(s, targetDir, metaClass);
 
@@ -780,7 +782,7 @@ bool QtDocGenerator::writeDocModifications(TextStream &s,
 {
     bool didSomething = false;
     for (const DocModification &mod : mods) {
-        if (mod.mode() == mode) {
+        if (mod.target() == DocumentationTarget::Documentation && mod.mode() == mode) {
             writeFormattedText(s, mod, scope, images);
             didSomething = true;
         }
@@ -841,7 +843,7 @@ QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type,
         return name;
 
     if (type.typeUsagePattern() == AbstractMetaType::PrimitivePattern) {
-        const auto &basicName = basicReferencedTypeEntry(type.typeEntry())->name();
+        const auto &basicName = type.basicPrimitiveName();
         if (AbstractMetaType::cppSignedIntTypes().contains(basicName)
             || AbstractMetaType::cppUnsignedIntTypes().contains(basicName)) {
             return intT;
@@ -1242,6 +1244,10 @@ void QtDocGenerator::writeModuleDocumentation()
         QtXmlToSphinxImages parsedImages;
         TextStream& s = output.stream;
 
+        TypeSystemTypeEntryCPtr typeSystemEntry = typeDb->findTypeSystemType(it.key());
+        Q_ASSERT(typeSystemEntry);
+        const auto docMode = typeSystemEntry->docMode();
+
         const QString &title = it.key();
         s << ".. module:: " << title << "\n\n" << headline(title, '*');
 
@@ -1281,8 +1287,10 @@ void QtDocGenerator::writeModuleDocumentation()
             << ":maxdepth: 1\n\n";
         if (hasGlobals)
             s << globalsPage << '\n';
-        for (const auto &e : std::as_const(docPackage.classPages))
-            s << e.file << '\n';
+        for (const auto &e : std::as_const(docPackage.classPages)) {
+            if (!e.nested || docMode == TypeSystem::DocMode::Flat)
+                s << e.file << '\n';
+        }
         s << "\n\n" << outdent << outdent << headline("Detailed Description");
 
         // module doc is always wrong and C++istic, so go straight to the extra directory!
@@ -1324,10 +1332,7 @@ void QtDocGenerator::writeModuleDocumentation()
               << moduleDocumentation.qmlTypesUrl << ">`_\n\n";
         }
 
-        TypeSystemTypeEntryCPtr typeSystemEntry = typeDb->findTypeSystemType(it.key());
-        Q_ASSERT(typeSystemEntry);
-        writeFancyToc(s, "List of Classes", classEntryListToToc(docPackage.classPages,
-                                                                typeSystemEntry->docMode()),
+        writeFancyToc(s, "List of Classes", classEntryListToToc(docPackage.classPages, docMode),
                       "class"_L1);
         writeFancyToc(s, "List of Decorators", fileListToToc(docPackage.decoratorPages),
                       "deco"_L1);
@@ -1791,7 +1796,7 @@ static QString imageRelativeTargetDirFromContext(const QString &scope,
 ResolvedDocImage
 QtDocGenerator::resolveImage(const QtXmlToSphinxImage &image,
                              const QStringList &sourceDirs,
-                             const QString &targetDir) const
+                             const QString &targetDir)
 {
     QString hrefBase;
     QString hrefName = image.href; // split "images/a.png"
@@ -1871,7 +1876,7 @@ static void copyParsedImage(const ResolvedDocImage &image, QDir &targetDir)
 // Copy parsed images from WebXML to doc/base
 void QtDocGenerator::copyParsedImages(const QtXmlToSphinxImages &images,
                                       const QStringList &sourceDocumentFiles,
-                                      const QString &targetDocumentDir) const
+                                      const QString &targetDocumentDir)
 {
     if (images.isEmpty())
         return;
index ce6a5f4a09df23147a0e57654abad0237883c495..8937814a3d080a16deb9d23297c137492623feca 100644 (file)
@@ -3,9 +3,9 @@
 #ifndef DOCGENERATOR_H
 #define DOCGENERATOR_H
 
-#include <QtCore/QStringList>
-#include <QtCore/QMap>
-#include <QtCore/QScopedPointer>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qscopedpointer.h>
 
 #include "generator.h"
 #include "documentation.h"
@@ -129,12 +129,12 @@ private:
                       const DocPackage &docPackage);
     void writeAdditionalDocumentation() const;
     bool writeInheritanceFile();
-    ResolvedDocImage resolveImage(const QtXmlToSphinxImage &image,
-                                  const QStringList &sourceDirs,
-                                  const QString &targetDir) const;
-    void copyParsedImages(const QtXmlToSphinxImages &images,
-                          const QStringList &sourceDocumentFiles,
-                          const QString &targetDocumentFile) const;
+    static ResolvedDocImage resolveImage(const QtXmlToSphinxImage &image,
+                                         const QStringList &sourceDirs,
+                                         const QString &targetDir);
+    static void copyParsedImages(const QtXmlToSphinxImages &images,
+                                 const QStringList &sourceDocumentFiles,
+                                 const QString &targetDocumentFile);
     QString translateToPythonType(const AbstractMetaType &type,
                                   const AbstractMetaClassCPtr &cppClass,
                                   bool createRef = true) const;
index a60a288daef70e354fb69fcf7bfeb0da6f126e27..bddf2f51177e90afe142a718fe35714ab1335e03 100644 (file)
@@ -3,19 +3,20 @@
 
 #include "qtxmltosphinx.h"
 #include "exception.h"
+#include <filecache.h>
 #include "qtxmltosphinxinterface.h"
 #include <codesniphelpers.h>
 #include "rstformat.h"
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFileInfo>
-#include <QtCore/QHash>
-#include <QtCore/QLoggingCategory>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QXmlStreamReader>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qxmlstream.h>
 
 using namespace Qt::StringLiterals;
 
@@ -562,7 +563,7 @@ static QString resolveFile(const QStringList &locations, const QString &path)
         if (QFileInfo::exists(location))
             return location;
     }
-    return QString();
+    return {};
 }
 
 enum class SnippetType
@@ -605,7 +606,7 @@ QtXmlToSphinx::Snippet QtXmlToSphinx::readSnippetFromLocations(const QString &pa
     // use existing fallback paths first.
     const auto type = snippetType(path);
     if (type == SnippetType::Other && !fallbackPath.isEmpty()) {
-        const QString code = readFromLocation(fallbackPath, identifier, errorMessage);
+        const QString code = readSnippet(fallbackPath, identifier, errorMessage);
         return {code, code.isNull() ? Snippet::Error : Snippet::Fallback};
     }
 
@@ -620,7 +621,7 @@ QtXmlToSphinx::Snippet QtXmlToSphinx::readSnippetFromLocations(const QString &pa
             if (!rewrittenPath.isEmpty()) {
                 rewrittenPath.replace(m_parameters.codeSnippetRewriteOld,
                                       m_parameters.codeSnippetRewriteNew);
-                const QString code = readFromLocation(rewrittenPath, identifier, errorMessage);
+                const QString code = readSnippet(rewrittenPath, identifier, errorMessage);
                 m_containsAutoTranslations = true;
                 return {code, code.isNull() ? Snippet::Error : Snippet::Converted};
             }
@@ -628,20 +629,20 @@ QtXmlToSphinx::Snippet QtXmlToSphinx::readSnippetFromLocations(const QString &pa
 
         resolvedPath = resolveFile(locations, pySnippetName(path, type));
         if (!resolvedPath.isEmpty()) {
-            const QString code = readFromLocation(resolvedPath, identifier, errorMessage);
+            const QString code = readSnippet(resolvedPath, identifier, errorMessage);
             return {code, code.isNull() ? Snippet::Error : Snippet::Converted};
         }
     }
 
     resolvedPath = resolveFile(locations, path);
     if (!resolvedPath.isEmpty()) {
-        const QString code = readFromLocation(resolvedPath, identifier, errorMessage);
+        const QString code = readSnippet(resolvedPath, identifier, errorMessage);
         return {code, code.isNull() ? Snippet::Error : Snippet::Resolved};
     }
 
     if (!fallbackPath.isEmpty()) {
         *errorMessage = msgFallbackWarning(path, identifier, fallbackPath);
-        const QString code = readFromLocation(fallbackPath, identifier, errorMessage);
+        const QString code = readSnippet(fallbackPath, identifier, errorMessage);
         return {code, code.isNull() ? Snippet::Error : Snippet::Fallback};
     }
 
@@ -649,102 +650,39 @@ QtXmlToSphinx::Snippet QtXmlToSphinx::readSnippetFromLocations(const QString &pa
     return {{}, Snippet::Error};
 }
 
-static QString msgSnippetNotFound(const QIODevice &inputFile,
-                                  const QString &identifier)
-{
-    return u"Code snippet file found ("_s + fileNameOfDevice(&inputFile)
-           + u"), but snippet ["_s + identifier + u"] not found."_s;
-}
-
-static QString msgEmptySnippet(const QIODevice &inputFile, int lineNo,
+static QString msgEmptySnippet(const QString &inputFile,
                                const QString &identifier)
 {
-    return u"Empty code snippet ["_s + identifier + u"] at "_s
-           + fileNameOfDevice(&inputFile) + u':' + QString::number(lineNo);
+    return "Empty code snippet ["_L1 + identifier + "] in "_L1
+           + QDir::toNativeSeparators(inputFile);
 }
 
-// Pattern to match qdoc snippet IDs with "#/// [id]" comments and helper to find ID
-static const QRegularExpression &snippetIdPattern()
+// Pattern to match qdoc snippet IDs "#/// [id]" or "# ![id1] # ![id2]"
+static QRegularExpression snippetIdPattern(const QString &snippetId)
 {
-    static const QRegularExpression result(uR"RX((//|#) *! *\[([^]]+)\])RX"_s);
+    QString pattern = "(//|#) *! *\\["_L1
+                      + QRegularExpression::escape(snippetId) + "\\]"_L1;
+    QRegularExpression result(pattern);
     Q_ASSERT(result.isValid());
     return result;
 }
 
-static bool matchesSnippetId(QRegularExpressionMatchIterator it,
-                             const QString &identifier)
-{
-    while (it.hasNext()) {
-        if (it.next().captured(2) == identifier)
-            return true;
-    }
-    return false;
-}
-
-QString QtXmlToSphinx::readSnippet(QIODevice &inputFile, const QString &identifier,
+QString QtXmlToSphinx::readSnippet(const QString &location, const QString &identifier,
                                    QString *errorMessage)
 {
-    const QByteArray identifierBA = identifier.toUtf8();
-    // Lambda that matches the snippet id
-    const auto snippetIdPred = [&identifierBA, &identifier](const QByteArray &lineBA)
-    {
-        const bool isComment = lineBA.contains('/') || lineBA.contains('#');
-        if (!isComment || !lineBA.contains(identifierBA))
-            return false;
-        const QString line = QString::fromUtf8(lineBA);
-        return matchesSnippetId(snippetIdPattern().globalMatch(line), identifier);
-    };
+    static FileCache cache;
 
-    // Find beginning, skip over
-    int lineNo = 1;
-    for (; !inputFile.atEnd() && !snippetIdPred(inputFile.readLine());
-         ++lineNo) {
-    }
-
-    if (inputFile.atEnd()) {
-        *errorMessage = msgSnippetNotFound(inputFile, identifier);
+    const auto result = identifier.isEmpty() ? cache.fileContents(location)
+        : cache.fileSnippet(location, identifier, snippetIdPattern(identifier));
+    if (!result.has_value()) {
+        *errorMessage = cache.errorString();
         return {};
     }
 
-    QString code;
-    for (; !inputFile.atEnd(); ++lineNo) {
-        const QString line = QString::fromUtf8(inputFile.readLine());
-        auto it = snippetIdPattern().globalMatch(line);
-        if (it.hasNext()) { // Skip snippet id lines
-            if (matchesSnippetId(it, identifier))
-                break;
-        } else {
-            code += line;
-        }
-    }
-
-    if (code.isEmpty())
-        *errorMessage = msgEmptySnippet(inputFile, lineNo, identifier);
-
-    return code;
-}
-
-QString QtXmlToSphinx::readFromLocation(const QString &location, const QString &identifier,
-                                        QString *errorMessage)
-{
-    QFile inputFile;
-    inputFile.setFileName(location);
-    if (!inputFile.open(QIODevice::ReadOnly)) {
-        QTextStream(errorMessage) << "Could not read code snippet file: "
-            << QDir::toNativeSeparators(inputFile.fileName())
-            << ": " << inputFile.errorString();
-        return {}; // null
-    }
-
-    QString code = u""_s; // non-null
-    if (identifier.isEmpty()) {
-        while (!inputFile.atEnd())
-            code += QString::fromUtf8(inputFile.readLine());
-        return CodeSnipHelpers::fixSpaces(code);
-    }
+    if (result.value().isEmpty())
+        *errorMessage = msgEmptySnippet(location, identifier);
 
-    code = readSnippet(inputFile, identifier, errorMessage);
-    return code.isEmpty() ? QString{} : CodeSnipHelpers::fixSpaces(code); // maintain isNull()
+    return CodeSnipHelpers::fixSpaces(result.value());
 }
 
 void QtXmlToSphinx::handleHeadingTag(QXmlStreamReader& reader)
@@ -990,7 +928,7 @@ void QtXmlToSphinx::handleSnippetTag(QXmlStreamReader& reader)
 
         if (m_parameters.snippetComparison && snippet.result == Snippet::Converted
             && !fallbackPath.isEmpty()) {
-            const QString fallbackCode = readFromLocation(fallbackPath, identifier, &errorMessage);
+            const QString fallbackCode = readSnippet(fallbackPath, identifier, &errorMessage);
             debug(msgSnippetComparison(location, identifier, snippet.code, fallbackCode));
         }
 
@@ -1423,7 +1361,7 @@ void QtXmlToSphinx::handleQuoteFileTag(QXmlStreamReader& reader)
         QString location = reader.text().toString();
         location.prepend(m_parameters.libSourceDir + u'/');
         QString errorMessage;
-        QString code = readFromLocation(location, QString(), &errorMessage);
+        QString code = readSnippet(location, QString(), &errorMessage);
         if (!errorMessage.isEmpty())
             warn(msgTagWarning(reader, m_context, m_lastTagName, errorMessage));
         m_output << "::\n\n";
@@ -1610,7 +1548,7 @@ void QtXmlToSphinx::Table::formatDebug(QDebug &debug) const
 
 void QtXmlToSphinx::stripPythonQualifiers(QString *s)
 {
-    const int lastSep = s->lastIndexOf(u'.');
+    const auto lastSep = s->lastIndexOf(u'.');
     if (lastSep != -1)
         s->remove(0, lastSep + 1);
 }
index 1cd07a6a1199a4dfe1b352db20c2d449b70ebf43..757013925ac8739162a878bfe15a43f0dd63fbeb 100644 (file)
@@ -6,9 +6,9 @@
 
 #include <textstream.h>
 
-#include <QtCore/QList>
-#include <QtCore/QScopedPointer>
-#include <QtCore/QStack>
+#include <QtCore/qlist.h>
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qstack.h>
 
 #include <memory>
 
@@ -104,8 +104,7 @@ public:
 
     static void stripPythonQualifiers(QString *s);
 
-    // For testing
-    static QString readSnippet(QIODevice &inputFile, const QString &identifier,
+    static QString readSnippet(const QString &location, const QString &identifier,
                                QString *errorMessage);
 
 private:
@@ -203,8 +202,6 @@ private:
                                      const QString &identifier,
                                      const QString &fallbackPath,
                                      QString *errorMessage);
-    static QString readFromLocation(const QString &location, const QString &identifier,
-                             QString *errorMessage);
     void pushOutputBuffer();
     QString popOutputBuffer();
     void writeTable(Table& table);
index dbef86f42512693b71d459720612f9987a48a856..3b3524914cfa5f9167f7f261b2bcf1ef867fd81f 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef QTXMLTOSPHINXINTERFACE_H
 #define QTXMLTOSPHINXINTERFACE_H
 
-#include <QtCore/QStringList>
+#include <QtCore/qstringlist.h>
 
 QT_FORWARD_DECLARE_CLASS(QLoggingCategory)
 QT_FORWARD_DECLARE_CLASS(QDebug)
index 8af7671fb809d4c080b11a72b01bb4973248368f..d0d6162affeb1916c941acf7a332ddf2d4d5ae0c 100644 (file)
@@ -6,10 +6,10 @@
 
 #include <textstream.h>
 
-#include <QtCore/QByteArray>
-#include <QtCore/QString>
-#include <QtCore/QTextStream>
-#include <QtCore/QVersionNumber>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qversionnumber.h>
 
 struct rstVersionAdded
 {
index 9eb1f2c13808427964d1f5afa0554b511851428b..654b502103625f442d2b4a10dba0059f072023eb 100644 (file)
 
 #include "qtcompat.h"
 
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QMetaObject>
-#include <QtCore/QMetaType>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qmetatype.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qset.h>
+#include <QtCore/qtextstream.h>
 
 #include <algorithm>
 #include <cstring>
@@ -481,7 +482,7 @@ static void writePyGetSetDefEntry(TextStream &s, const QString &name,
 
 static bool generateRichComparison(const GeneratorContext &c)
 {
-    const auto metaClass = c.metaClass();
+    const auto &metaClass = c.metaClass();
     if (c.forSmartPointer()) {
         auto te = std::static_pointer_cast<const SmartPointerTypeEntry>(metaClass->typeEntry());
         return te->smartPointerType() == TypeSystem::SmartPointerType::Shared;
@@ -494,7 +495,7 @@ void CppGenerator::generateIncludes(TextStream &s, const GeneratorContext &class
                                     const IncludeGroupList &includes,
                                     const AbstractMetaClassCList &innerClasses) const
 {
-    const auto metaClass = classContext.metaClass();
+    const auto &metaClass = classContext.metaClass();
 
     // write license comment
     s << licenseComment() << '\n';
@@ -549,6 +550,16 @@ void CppGenerator::generateIncludes(TextStream &s, const GeneratorContext &class
     s << "\n// main header\n" << "#include \""
       << HeaderGenerator::headerFileNameForContext(classContext) << "\"\n";
 
+    // Wrappers from which overrides are re-used.
+    QSet<QString> wrapperIncludes;
+    for (const auto &f : getReusedOverridenFunctions(metaClass))
+        wrapperIncludes.insert(ShibokenGenerator::headerFileNameForClass(f->ownerClass()));
+    if (!wrapperIncludes.isEmpty()) {
+        s  << "\n// Wrappers providing overrides\n";
+        for (const auto &wrapperInclude : std::as_const(wrapperIncludes))
+            s << "#include \"" << wrapperInclude << "\"\n";
+    }
+
     if (!innerClasses.isEmpty()) {
         s  << "\n// inner classes\n";
         for (const auto &innerClass : innerClasses) {
@@ -617,6 +628,31 @@ static void writeAddedTypeSignatures(TextStream &s, const ComplexTypeEntryCPtr &
     }
 }
 
+// Print diagnostics/warnings about special types
+static void warnAboutTypes(const AbstractMetaClassCPtr &metaClass)
+{
+    const auto typeEntry = metaClass->typeEntry();
+    if (typeEntry->isValue()) {
+        // Warn about special value types.
+        if (!typeEntry->isDefaultConstructible()
+            && typeEntry->defaultConstructibleFlag() == TypeSystem::DefaultConstructibleFlag::Unspecified) {
+            ReportHandler::addGeneralMessage("Value type \""_L1 + typeEntry->qualifiedCppName()
+                                             + "\" is not default constructible."_L1);
+        }
+        if (!typeEntry->isCopyable()
+            && typeEntry->copyableFlag() == TypeSystem::CopyableFlag::Unspecified) {
+            ReportHandler::addGeneralMessage("Value type \""_L1 + typeEntry->qualifiedCppName()
+                                             + "\" is not copyable, std::move() will be used."_L1);
+        }
+    } else if (typeEntry->isObject())
+        if (metaClass->isDefaultConstructible()
+            && metaClass->isCopyConstructible() && !metaClass->isPolymorphic()
+            && metaClass->baseClass() == nullptr) {
+        qCWarning(lcShiboken, "Object type \"%s\" has default and copy constructors; consider using value-type.",
+                  qPrintable(typeEntry->qualifiedCppName()));
+    }
+}
+
 /// Function used to write the class generated binding code on the buffer
 /// \param s the output buffer
 /// \param classContext the pointer to metaclass information
@@ -626,9 +662,11 @@ void CppGenerator::generateClass(TextStream &s,
                                  QList<GeneratorContext> *)
 {
     s.setLanguage(TextStream::Language::Cpp);
-    AbstractMetaClassCPtr metaClass = classContext.metaClass();
+    const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
     const auto typeEntry = metaClass->typeEntry();
 
+    warnAboutTypes(metaClass);
+
     auto innerClasses = metaClass->innerClasses();
     for (auto it = innerClasses.begin(); it != innerClasses.end(); ) {
         auto innerTypeEntry = (*it)->typeEntry();
@@ -691,14 +729,16 @@ void CppGenerator::generateClass(TextStream &s,
             s << outdent << "}\n\n";
         }
 
-        int maxOverrides = 0;
+        const auto &wrapperConstructors = ShibokenGenerator::getWrapperConstructors(metaClass);
+
         if (useOverrideCaching(classContext.metaClass()))
             writeCacheResetNative(s, classContext);
+        for (const auto &func : wrapperConstructors)
+            writeConstructorNative(s, classContext, func);
+        int maxOverrides = 0;
         for (const auto &func : metaClass->functions()) {
             const auto generation = functionGeneration(func);
-            if (generation.testFlag(FunctionGenerationFlag::WrapperConstructor))
-                writeConstructorNative(s, classContext, func);
-            else if (generation.testFlag(FunctionGenerationFlag::VirtualMethod))
+            if (generation.testFlag(FunctionGenerationFlag::VirtualMethod))
                 writeVirtualMethodNative(s, func, maxOverrides++);
         }
 
@@ -717,6 +757,18 @@ void CppGenerator::generateClass(TextStream &s,
 
     s << openTargetExternC;
 
+    const auto &constructors = getConstructors(metaClass);
+    if (constructors.isEmpty()) {
+        const char *errorFunc = classContext.metaClass()->isNamespace()
+            ? "setInstantiateNamespace" : "setInstantiateNonConstructible";
+        writeConstructorDummy(s, classContext, errorFunc);
+    } else {
+        OverloadData overloadData(constructors, api());
+        writeConstructorWrapper(s, overloadData, classContext);
+        // On constructors, we also generate the property initializers.
+        writeSignatureInfo(signatureStream, overloadData, true);
+    }
+
     const auto &functionGroups = getFunctionGroups(metaClass);
     for (auto it = functionGroups.cbegin(), end = functionGroups.cend(); it != end; ++it) {
         if (contains(sequenceProtocols(), it.key()) || contains(mappingProtocols(), it.key()))
@@ -728,13 +780,8 @@ void CppGenerator::generateClass(TextStream &s,
         const auto &rfunc = overloads.constFirst();
         OverloadData overloadData(overloads, api());
 
-        if (rfunc->isConstructor()) {
-            writeConstructorWrapper(s, overloadData, classContext);
-            // On constructors, we also generate the property initializers.
-            writeSignatureInfo(signatureStream, overloadData, true);
-        }
         // call operators
-        else if (rfunc->name() == u"operator()") {
+        if (rfunc->name() == u"operator()") {
             writeMethodWrapper(s, overloadData, classContext);
             writeSignatureInfo(signatureStream, overloadData);
         }
@@ -938,8 +985,7 @@ void CppGenerator::writeCacheResetNative(TextStream &s, const GeneratorContext &
 void CppGenerator::writeConstructorNative(TextStream &s, const GeneratorContext &classContext,
                                           const AbstractMetaFunctionCPtr &func) const
 {
-    const QString qualifiedName = classContext.wrapperName() + u"::"_s;
-    s << functionSignature(func, qualifiedName, QString(),
+    s << functionSignature(func, classContext.wrapperName(), {},
                            OriginalTypeDescription | SkipDefaultValues);
     if (!func->arguments().isEmpty()) {
         s << " : ";
@@ -1020,15 +1066,14 @@ QString CppGenerator::getVirtualFunctionReturnTypeName(const AbstractMetaFunctio
         + typeEntry->qualifiedCppName() + u" >()->tp_name"_s;
 }
 
-// When writing an overridden method of a wrapper class, write the part
-// calling the C++ function in case no overload in Python exists.
 void CppGenerator::writeVirtualMethodCppCall(TextStream &s,
                                              const AbstractMetaFunctionCPtr &func,
                                              const QString &funcName,
                                              const CodeSnipList &snips,
                                              const AbstractMetaArgument *lastArg,
                                              const TypeEntryCPtr &retType,
-                                             const QString &returnStatement, bool hasGil) const
+                                             const QString &returnStatement,
+                                             bool ownsGil, bool hasGilVar) const
 {
     if (!snips.isEmpty()) {
         writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning,
@@ -1036,15 +1081,15 @@ void CppGenerator::writeVirtualMethodCppCall(TextStream &s,
     }
 
     if (func->isAbstract()) {
-        if (!hasGil)
-            s << "Shiboken::GilState gil;\n";
+        if (!ownsGil)
+            s << (hasGilVar ? "gil.acquire();\n" : "Shiboken::GilState gil;\n");
         s << "Shiboken::Errors::setPureVirtualMethodError(\""
             << func->ownerClass()->name() << '.' << funcName << "\");\n"
             << returnStatement << '\n';
         return;
     }
 
-    if (hasGil)
+    if (ownsGil)
         s << "gil.release();\n";
 
     if (retType)
@@ -1129,8 +1174,7 @@ std::pair<QString, QChar> CppGenerator::virtualMethodNativeArg(const AbstractMet
     auto argTypeEntry = type.typeEntry();
     // Check for primitive types convertible by Py_BuildValue()
     if (argTypeEntry->isPrimitive() && !type.isCString()) {
-        const auto pte = basicReferencedTypeEntry(argTypeEntry);
-        auto it = formatUnits().constFind(pte->name());
+        auto it = formatUnits().constFind(type.basicPrimitiveName());
         if (it != formatUnits().constEnd())
             return {arg.name(), it.value()};
     }
@@ -1252,9 +1296,9 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s,
     const QString funcName = func->isOperatorOverload()
         ? pythonOperatorFunctionName(func) : func->definitionNames().constFirst();
 
-    QString prefix = wrapperName(func->ownerClass()) + u"::"_s;
-    s << functionSignature(func, prefix, QString(), Generator::SkipDefaultValues |
-                                                    Generator::OriginalTypeDescription)
+    QString className = wrapperName(func->ownerClass());
+    const Options options = Generator::SkipDefaultValues | Generator::OriginalTypeDescription;
+    s << functionSignature(func, className, {}, options)
       << "\n{\n" << indent;
 
     const auto returnStatement = virtualMethodReturn(api(), func,
@@ -1284,46 +1328,57 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s,
     if (wrapperDiagnostics()) {
         s << "std::cerr << ";
 #ifndef Q_CC_MSVC // g++ outputs __FUNCTION__ unqualified
-        s << '"' << prefix << R"(" << )";
+        s << '"' << className << R"(::" << )";
 #endif
         s  << R"(__FUNCTION__ << ' ' << this << " m_PyMethodCache[" << )"
            << cacheIndex << R"( << "]=" << m_PyMethodCache[)" << cacheIndex
            << R"(] << '\n';)" << '\n';
     }
-    // PYSIDE-803: Build a boolean cache for unused overrides
-    const bool multi_line = func->isVoid() || !snips.isEmpty() || isAbstract;
-    s << "if (m_PyMethodCache[" << cacheIndex << "])" << (multi_line ? " {\n" : "\n")
-        << indent;
-    writeVirtualMethodCppCall(s, func, funcName, snips, lastArg, retType,
-                              returnStatement.statement, false);
-    s << outdent;
-    if (multi_line)
-        s << "}\n";
-
-    s << "Shiboken::GilState gil;\n";
-
-    // Get out of virtual method call if someone already threw an error.
-    s << "if (" << shibokenErrorsOccurred << ")\n" << indent
-        << returnStatement.statement << '\n' << outdent;
-
-    s << "static PyObject *nameCache[2] = {};\n";
     writeFuncNameVar(s, func, funcName);
-    s << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR
-        << "(Shiboken::BindingManager::instance().getOverride(this, nameCache, funcName));\n"
-        << "if (" << PYTHON_OVERRIDE_VAR << ".isNull()) {\n" << indent
-        << "m_PyMethodCache[" << cacheIndex << "] = true;\n";
+    s << "static PyObject *nameCache[2] = {};\n"
+      << "Shiboken::GilState gil(false);\n"
+      << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR << "(Sbk_GetPyOverride("
+      << "this, gil, funcName, &m_PyMethodCache[" << cacheIndex << "], nameCache));\n"
+      << "if (pyOverride.isNull()) {\n" << indent;
     writeVirtualMethodCppCall(s, func, funcName, snips, lastArg, retType,
-                              returnStatement.statement, true);
-    s << outdent << "}\n\n"; //WS
-
+                              returnStatement.statement, false, true);
+    s << outdent << "}\n";
     if (!snips.isEmpty()) {
         writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionPyOverride,
                        TypeSystem::ShellCode, func, false, lastArg);
     }
 
+    // Call the (static) Python override implementation
+    if (!func->isVoid())
+        s << "return ";
+
+    auto owner = func->ownerClass();
+    const auto &reusedFuncs = getReusedOverridenFunctions(owner);
+    auto rit = reusedFuncs.constFind(func);
+    const bool canReuse = rit != reusedFuncs.cend();
+    if (canReuse) {
+        s << wrapperName(rit.value()->ownerClass()) << "::"
+            << pythonOverrideImplName(rit.value());
+    } else {
+        s << pythonOverrideImplName(func);
+    }
+    s << "(\"" << owner->name()
+        << "\", funcName, gil, " << PYTHON_OVERRIDE_VAR;
+    for (const auto &arg : func->arguments())
+        s << ", " << (arg.type().useStdMove() ? stdMove(arg.name()) : arg.name());
+    s << ");\n" << outdent << "}\n\n";
+
+    if (canReuse)
+        return;
+    // Write Python override implementation definition
+    s << functionSignature(func, className, {}, options | PythonOverrideImplementation)
+        << "\n{\n" << indent
+        << sbkUnusedVariableCast("ownerClassName") << sbkUnusedVariableCast("funcName")
+        << sbkUnusedVariableCast("gil") << sbkUnusedVariableCast(PYTHON_OVERRIDE_VAR);
+    if (returnStatement.needsReference)
+        writeVirtualMethodStaticReturnVar(s, func);
     writeVirtualMethodPythonOverride(s, func, snips, returnStatement);
 }
-
 void CppGenerator::writeVirtualMethodPythonOverride(TextStream &s,
                                                     const AbstractMetaFunctionCPtr &func,
                                                     const CodeSnipList &snips,
@@ -1422,8 +1477,7 @@ void CppGenerator::writeVirtualMethodPythonOverride(TextStream &s,
 
         s << "if (" << PYTHON_RETURN_VAR << ".isNull()) {\n" << indent
             << "// An error happened in python code!\n"
-            << "Shiboken::Errors::storePythonOverrideErrorOrPrint(\""
-            << func->ownerClass()->name() << "\", funcName);\n"
+            << "Shiboken::Errors::storePythonOverrideErrorOrPrint(ownerClassName, funcName);\n"
             << returnStatement.statement << "\n" << outdent
         << "}\n";
 
@@ -1447,8 +1501,7 @@ void CppGenerator::writeVirtualMethodPythonOverride(TextStream &s,
                         << cpythonIsConvertibleFunction(func->type())
                         << PYTHON_RETURN_VAR << ");\n" << outdent
                     << "if (!" << PYTHON_TO_CPP_VAR << ") {\n" << indent
-                        << "Shiboken::Warnings::warnInvalidReturnValue(\""
-                        << func->ownerClass()->name() << "\", funcName, "
+                        << "Shiboken::Warnings::warnInvalidReturnValue(ownerClassName, funcName, "
                         << getVirtualFunctionReturnTypeName(func) << ", "
                         << "Py_TYPE(" << PYTHON_RETURN_VAR << ")->tp_name);\n"
                         << returnStatement.statement << '\n' << outdent
@@ -1469,8 +1522,7 @@ void CppGenerator::writeVirtualMethodPythonOverride(TextStream &s,
                     if (func->type().isPointerToWrapperType())
                         s << " && " << PYTHON_RETURN_VAR << " != Py_None";
                     s << ") {\n" << indent
-                        << "Shiboken::Warnings::warnInvalidReturnValue(\""
-                        << func->ownerClass()->name() << "\", funcName, "
+                        << "Shiboken::Warnings::warnInvalidReturnValue(ownerClassName, funcName, "
                         << getVirtualFunctionReturnTypeName(func) << ", "
                         << "Py_TYPE(" << PYTHON_RETURN_VAR << ")->tp_name);\n"
                         << returnStatement.statement << '\n' << outdent
@@ -1541,13 +1593,13 @@ void CppGenerator::writeUserAddedPythonOverride(TextStream &s,
     const CodeSnipList snips = func->hasInjectedCode()
         ? func->injectedCodeSnips() : CodeSnipList();
 
-    QString prefix = wrapperName(func->ownerClass()) + u"::"_s;
-    s << '\n' << functionSignature(func, prefix, QString(), Generator::SkipDefaultValues |
+    s << '\n' << functionSignature(func, wrapperName(func->ownerClass()), {},
+                                   Generator::SkipDefaultValues |
                                    Generator::OriginalTypeDescription)
       << "\n{\n" << indent << sbkUnusedVariableCast("gil");
 
     writeFuncNameVar(s, func, funcName);
-
+    s << "const char ownerClassName[] = \"" << func->ownerClass()->name() << "\";\n";
     const auto returnStatement = virtualMethodReturn(api(), func,
                                                      func->modifications());
     writeVirtualMethodPythonOverride(s, func, snips, returnStatement);
@@ -1723,6 +1775,13 @@ const char *typeName = )";
         << cpythonType << ", const_cast<void *>(cppIn), false, typeName);\n";
 }
 
+// Either copy or generate a special converter using std::move for std::unique_ptr
+static bool hasCopyConverter(const AbstractMetaClassCPtr &metaClass)
+{
+    const auto &te = metaClass->typeEntry();
+    return !te->isObject() && (te->isCopyable() || te->isMovable());
+}
+
 void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClassCPtr &metaClass,
                                            const GeneratorContext &classContext) const
 {
@@ -1774,7 +1833,7 @@ void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClas
     writeCppToPythonFunction(s, c.toString(), sourceTypeName, targetTypeName);
 
     // The conversions for an Object Type end here.
-    if (!typeEntry->isValue() && !typeEntry->isSmartPointer()) {
+    if (!hasCopyConverter(metaClass)) {
         s << '\n';
         return;
     }
@@ -1787,10 +1846,8 @@ void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClas
 
     c.clear();
 
-    const bool isUniquePointer = classContext.forSmartPointer()
-        && typeEntry->isUniquePointer();
-
-    if (isUniquePointer) {
+    const bool needsMove =  metaClass->typeEntry()->isMoveOnlyType();
+    if (needsMove) {
         c << "auto *source = reinterpret_cast<" << typeName
             << " *>(const_cast<void *>(cppIn));\n";
     } else {
@@ -1798,7 +1855,7 @@ void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClas
     }
     c << "return Shiboken::Object::newObject(" << cpythonType
         << ", new " << globalScopePrefix(classContext) << classContext.effectiveClassName() << '('
-        << (isUniquePointer ? "std::move(*source)" : "*source")
+        << (needsMove ? "std::move(*source)" : "*source")
         << "), true, true);";
     writeCppToPythonFunction(s, c.toString(), sourceTypeName, targetTypeName);
     s << '\n';
@@ -1813,8 +1870,8 @@ void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClas
     QString pyInVariable = u"pyIn"_s;
     const QString outPtr = u"reinterpret_cast<"_s + typeName + u" *>(cppOut)"_s;
     if (!classContext.forSmartPointer()) {
-        c << '*' << outPtr << " = *"
-            << cpythonWrapperCPtr(typeEntry, pyInVariable) << ';';
+        QString value = u'*' + cpythonWrapperCPtr(typeEntry, pyInVariable);
+        c << '*' << outPtr << " = " << (needsMove ? stdMove(value) : value) << ';';
     } else {
         auto ste = std::static_pointer_cast<const SmartPointerTypeEntry>(typeEntry);
         const QString resetMethod = ste->resetMethod();
@@ -1826,7 +1883,7 @@ void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClas
             c << "ptr->" << resetMethod << "();\n";
         const QString value = u'*' + cpythonWrapperCPtr(classContext.preciseType(), pyInVariable);
         c << outdent << "else\n" << indent
-            << "*ptr = " << (isUniquePointer ? stdMove(value) : value) << ';';
+            << "*ptr = " << (needsMove ? stdMove(value) : value) << ';';
     }
 
     writePythonToCppFunction(s, c.toString(), sourceTypeName, targetTypeName);
@@ -1942,7 +1999,8 @@ void CppGenerator::writeConverterRegister(TextStream &s, const AbstractMetaClass
         << convertibleToCppFunctionName(sourceTypeName, targetTypeName) << ',' << '\n';
     std::swap(targetTypeName, sourceTypeName);
     s << cppToPythonFunctionName(sourceTypeName, targetTypeName);
-    if (typeEntry->isValue() || typeEntry->isSmartPointer()) {
+
+    if (hasCopyConverter(metaClass)) {
         s << ',' << '\n';
         sourceTypeName = metaClass->name() + u"_COPY"_s;
         s << cppToPythonFunctionName(sourceTypeName, targetTypeName);
@@ -1981,7 +2039,7 @@ void CppGenerator::writeConverterRegister(TextStream &s, const AbstractMetaClass
                                    registerConverterName::TypeId);
     }
 
-    if (!typeEntry->isValue() && !typeEntry->isSmartPointer())
+    if (!hasCopyConverter(metaClass))
         return;
 
     // Python to C++ copy (value, not pointer neither reference) conversion.
@@ -2150,6 +2208,19 @@ void CppGenerator::writeMethodWrapperPreamble(TextStream &s,
     }
 }
 
+// Non-constructible classes
+void CppGenerator::writeConstructorDummy(TextStream &s,
+                                         const GeneratorContext &classContext,
+                                         const char *errorMessageFunc)
+{
+    s << "static int " << cpythonConstructorName(classContext.metaClass())
+        << "(PyObject *, PyObject *, PyObject *)\n{\n" << indent
+        << "Shiboken::Errors::" << errorMessageFunc << "(\""
+        << classContext.metaClass()->name()
+        << "\");\nreturn -1;\n"
+        << outdent << "}\n\n";
+}
+
 void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &overloadData,
                                            const GeneratorContext &classContext) const
 {
@@ -2372,17 +2443,14 @@ void CppGenerator::writeMethodWrapper(TextStream &s, const OverloadData &overloa
 
     s << '\n';
 
-    writeFunctionReturnErrorCheckSection(s, ErrorReturn::Default,
-                                         hasReturnValue && !rfunc->isInplaceOperator());
-
     if (hasReturnValue) {
         if (rfunc->isInplaceOperator()) {
-            s << "Py_INCREF(self);\nreturn self;\n";
+            s << "return Sbk_ReturnFromPython_Self(self);\n";
         } else {
-            s << "return " << PYTHON_RETURN_VAR << ";\n";
+            s << "return Sbk_ReturnFromPython_Result(" << PYTHON_RETURN_VAR <<");\n";
         }
     } else {
-        s << "Py_RETURN_NONE;\n";
+        s << " return Sbk_ReturnFromPython_None();\n";
     }
 
     s<< outdent << "}\n\n";
@@ -2503,7 +2571,7 @@ void CppGenerator::writeCppSelfDefinition(TextStream &s,
         return;
     }
 
-    AbstractMetaClassCPtr metaClass = context.metaClass();
+    const AbstractMetaClassCPtr &metaClass = context.metaClass();
     const auto cppWrapper = context.metaClass()->cppWrapper();
     // In the Python method, use the wrapper to access the protected
     // functions.
@@ -2647,7 +2715,9 @@ void CppGenerator::writeTypeCheck(TextStream &s, const AbstractMetaType &argType
     if (!argType.typeEntry()->isCustom()) {
         typeCheck = u'(' + pythonToCppConverterForArgumentName(argumentName)
                     + u" = "_s + typeCheck + u"))"_s;
-        if (!isNumber && isCppPrimitive(argType.typeEntry())) {
+        if (!isNumber
+            && argType.typeUsagePattern() == AbstractMetaType::PrimitivePattern // no array
+            && isCppPrimitive(argType.typeEntry())) {
             typeCheck.prepend(cpythonCheckFunction(argType) + u'('
                               + argumentName + u") && "_s);
         }
@@ -2777,6 +2847,8 @@ static inline QString arrayHandleType(const AbstractMetaTypeList &nestedArrayTyp
             + ", "_L1
             + QString::number(nestedArrayTypes.constFirst().arrayElementCount())
             + u'>';
+    default:
+        break;
     }
     return {};
 }
@@ -3111,7 +3183,7 @@ void CppGenerator::writeOverloadedFunctionDecisorEngine(TextStream &s,
                     AbstractMetaClassCPtr ownerClass = func->ownerClass();
                     ComplexTypeEntryCPtr baseContainerType = ownerClass->typeEntry()->baseContainerType();
                     if (baseContainerType && baseContainerType == func->arguments().constFirst().type().typeEntry()
-                        && ownerClass->isCopyable()) {
+                        && ownerClass->typeEntry()->isCopyable()) {
                         tck << '!' << cpythonCheckFunction(ownerClass->typeEntry())
                             << pyArgName << ")\n" << indent << "&& " << outdent;
                     }
@@ -3245,11 +3317,7 @@ void CppGenerator::writeSingleFunctionCall(TextStream &s,
             } else if (!injectCodeCallsFunc && !func->isUserAdded() && !hasConversionRule) {
                 // When an argument is removed from a method signature and no other means of calling
                 // the method are provided (as with code injection) the generator must abort.
-                QString m;
-                QTextStream(&m) << "No way to call '" << func->ownerClass()->name()
-                    << "::" << func->signature()
-                    << "' with the modifications described in the type system.";
-                throw Exception(m);
+                throw Exception(msgCannotCall(func, argIdx, injectCodeCallsFunc, hasConversionRule));
             }
             removedArgs++;
             continue;
@@ -3321,7 +3389,7 @@ QString CppGenerator::convertibleToCppFunctionName(const TargetToNativeConversio
 }
 
 void CppGenerator::writeCppToPythonFunction(TextStream &s, const QString &code, const QString &sourceTypeName,
-                                            QString targetTypeName) const
+                                            const QString &targetTypeName) const
 {
 
     QString prettyCode = code;
@@ -3459,7 +3527,7 @@ void CppGenerator::writePythonToCppConversionFunctions(TextStream &s,
         : getFullTypeName(targetType.typeEntry());
     c << "*reinterpret_cast<" << fullTypeName << " *>(cppOut) = "
         << fullTypeName << '('
-        << (sourceType.isUniquePointer() ? stdMove(conversion) : conversion)
+        << (sourceType.useStdMove() ? stdMove(conversion) : conversion)
         << ");";
     QString sourceTypeName = fixedCppTypeName(sourceType);
     QString targetTypeName = fixedCppTypeName(targetType);
@@ -3642,49 +3710,53 @@ void CppGenerator::writeNamedArgumentResolution(TextStream &s,
         return;
     }
 
+    Q_ASSERT(usePyArgs);
+
+    const auto count = args.size();
     // PySide-535: Allow for empty dict instead of nullptr in PyPy
-    s << "if (kwds && PyDict_Size(kwds) > 0) {\n" << indent;
-    if (!force)
-        s << "PyObject *value{};\n";
-    s << "Shiboken::AutoDecRef kwds_dup(PyDict_Copy(kwds));\n";
-    for (const AbstractMetaArgument &arg : args) {
+    s << "if (kwds && PyDict_Size(kwds) > 0)"; //  {\n" << indent;
+    if (count == 0) {
+        s << indent << "\nerrInfo.reset(PyDict_Copy(kwds));\n" << outdent;
+        return;
+    }
+    s << " {\n" << indent
+        << "static const Shiboken::ArgumentNameIndexMapping mapping[" << count << "] = {";
+    for (qsizetype i = 0; i < count; ++i) {
+        const auto &arg = args.at(i);
         const int pyArgIndex = arg.argumentIndex()
-            - OverloadData::numberOfRemovedArguments(func, arg.argumentIndex());
-        QString pyArgName = usePyArgs ? pythonArgsAt(pyArgIndex)
-                                      : PYTHON_ARG;
-        QString pyKeyName = u"key_"_s + arg.name();
-        s << "static PyObject *const " << pyKeyName
-            << " = Shiboken::String::createStaticString(\"" << arg.name() << "\");\n"
-            << "if (PyDict_Contains(kwds, " << pyKeyName << ") != 0) {\n" << indent
-            << "value = PyDict_GetItem(kwds, " << pyKeyName << ");\n"
-            << "if (value != nullptr && " << pyArgName << " != nullptr ) {\n"
-            << indent << "errInfo.reset(" << pyKeyName << ");\n"
-            << "Py_INCREF(errInfo.object());\n"
-            << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
-            << ";\n" << outdent << "}\nif (value != nullptr) {\n" << indent
-            << pyArgName << " = value;\nif (!";
+                - OverloadData::numberOfRemovedArguments(func, arg.argumentIndex());
+        if (i > 0)
+            s << ", ";
+        s << "{\"" << arg.name() << "\", " << pyArgIndex << '}';
+    }
+
+    s << "};\n";
+
+    const char *mappingFunc = func->isConstructor() && isQObject(func->ownerClass())
+        ? "parseConstructorKeywordArguments" : "parseKeywordArguments";
+    s << "if (!Shiboken::" << mappingFunc << "(kwds, mapping, "
+        << count << ", errInfo, " << PYTHON_ARGS << ')' << indent;
+    for (qsizetype i = 0; i < count; ++i) {
+        const auto &arg = args.at(i);
+        const int pyArgIndex = arg.argumentIndex()
+                - OverloadData::numberOfRemovedArguments(func, arg.argumentIndex());
         const auto &type = arg.modifiedType();
+        const QString pyArgName = pythonArgsAt(pyArgIndex);
+        s << "\n|| ";
+        s << '(' << pyArgName << " != nullptr && !";
         writeTypeCheck(s, type, pyArgName, isNumber(type.typeEntry()), {});
-        s << ")\n" << indent
-            << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
-            << ";\n" << outdent << outdent
-            << "}\nPyDict_DelItem(kwds_dup, " << pyKeyName << ");\n"
-            << outdent << "}\n";
+        s << ')';
     }
+    s << outdent << ") {\n" << indent
+        << "Py_XINCREF(errInfo.object());\n" // PYSIDE-3133, 0 if conversion fails
+        << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
+        << ';' << outdent << "\n}\n";;
+
     // PYSIDE-1305: Handle keyword args correctly.
     // Normal functions handle their parameters immediately.
     // For constructors that are QObject, we need to delay that
     // until extra keyword signals and properties are handled.
-    s << "if (PyDict_Size(kwds_dup) > 0) {\n" << indent
-        << "errInfo.reset(kwds_dup.release());\n";
-    if (!(func->isConstructor() && isQObject(func->ownerClass()))) {
-        s << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
-            << ";\n";
-    } else {
-        s << "// fall through to handle extra keyword signals and properties\n";
-    }
-    s << outdent << "}\n"
-        << outdent << "}\n";
+    s << outdent << "}\n";
 }
 
 QString CppGenerator::argumentNameFromIndex(const ApiExtractorResult &api,
@@ -3695,12 +3767,15 @@ QString CppGenerator::argumentNameFromIndex(const ApiExtractorResult &api,
         return PYTHON_SELF_VAR;
     case 0:
         return PYTHON_RETURN_VAR;
-    case 1: { // Single argument?
-        OverloadData data(getFunctionGroups(func->implementingClass()).value(func->name()), api);
-        if (!data.pythonFunctionWrapperUsesListOfArguments())
-            return PYTHON_ARG;
+    case 1: // Single argument?
+        if (!func->isConstructor()) {
+            OverloadData data(getFunctionGroups(func->implementingClass()).value(func->name()), api);
+            if (!data.pythonFunctionWrapperUsesListOfArguments())
+                return PYTHON_ARG;
+        }
+        break;
+    default:
         break;
-    }
     }
     return pythonArgsAt(argIndex - 1);
 }
@@ -4483,24 +4558,27 @@ TextStream &operator<<(TextStream &str, const pyTypeSlotEntry &e)
     return str;
 }
 
+static inline bool isDocString(const DocModification &d)
+{
+    return d.target() == DocumentationTarget::DocString;
+}
+
+static QString docString(const AbstractMetaClassCPtr &metaClass)
+{
+    const auto docModifs = metaClass->typeEntry()->docModifications();
+    auto it = std::find_if(docModifs.cbegin(), docModifs.cend(), isDocString);
+    return it != docModifs.cend() ? it->code().trimmed() : QString{};
+}
+
 void CppGenerator::writeClassDefinition(TextStream &s,
                                         const AbstractMetaClassCPtr &metaClass,
                                         const GeneratorContext &classContext)
 {
-    QString tp_init;
     QString tp_new;
     QString tp_dealloc;
     QString tp_hash;
     QString tp_call;
     const QString className = chopType(cpythonTypeName(metaClass));
-    AbstractMetaFunctionCList ctors;
-    const auto &allCtors = metaClass->queryFunctions(FunctionQueryOption::AnyConstructor);
-    for (const auto &f : allCtors) {
-        if (!f->isPrivate() && !f->isModifiedRemoved()
-            && f->functionType() != AbstractMetaFunction::MoveConstructorFunction) {
-            ctors.append(f);
-        }
-    }
 
     bool onlyPrivCtor = !metaClass->hasNonPrivateConstructor();
 
@@ -4514,12 +4592,9 @@ void CppGenerator::writeClassDefinition(TextStream &s,
         tp_dealloc = metaClass->hasPrivateDestructor() ?
                      u"SbkDeallocWrapperWithPrivateDtor"_s :
                      u"Sbk_object_dealloc /* PYSIDE-832: Prevent replacement of \"0\" with subtype_dealloc. */"_s;
-        tp_init.clear();
     } else {
         tp_dealloc = isQApp
             ? u"&SbkDeallocQAppWrapper"_s : u"&SbkDeallocWrapper"_s;
-        if (!onlyPrivCtor && !ctors.isEmpty())
-            tp_init = cpythonConstructorName(metaClass);
     }
 
     const AttroCheck attroCheck = checkAttroFunctionNeeds(metaClass);
@@ -4620,7 +4695,7 @@ void CppGenerator::writeClassDefinition(TextStream &s,
         << pyTypeSlotEntry("Py_tp_iternext", m_tpFuncs.value(u"__next__"_s))
         << pyTypeSlotEntry("Py_tp_methods", className + u"_methods"_s)
         << pyTypeSlotEntry("Py_tp_getset", tp_getset)
-        << pyTypeSlotEntry("Py_tp_init", tp_init)
+        << pyTypeSlotEntry("Py_tp_init", cpythonConstructorName(metaClass))
         << pyTypeSlotEntry("Py_tp_new", tp_new);
     if (supportsSequenceProtocol(metaClass)) {
         s << "// type supports sequence protocol\n";
@@ -4634,9 +4709,16 @@ void CppGenerator::writeClassDefinition(TextStream &s,
         s << "// type supports number protocol\n";
         writeTypeAsNumberDefinition(s, metaClass);
     }
+
+    const QString ds = docString(metaClass);
+    if (!ds.isEmpty()) {
+        s << "{Py_tp_doc, " << outdent
+          << "const_cast<char *>(R\"DS(" << ds << ")DS\")" << indent << "},\n";
+    }
+
     s << "{0, " << NULL_PTR << "}\n" << outdent << "};\n";
 
-    int packageLevel = packageName().count(u'.') + 1;
+    const auto packageLevel = packageName().count(u'.') + 1;
     s << "static PyType_Spec " << className << "_spec = {\n" << indent
         << '"' << packageLevel << ':' << getClassTargetFullName(metaClass) << "\",\n"
         << "sizeof(SbkObject),\n0,\n" << tp_flags << ",\n"
@@ -4855,11 +4937,12 @@ QString CppGenerator::writeCopyFunction(TextStream &s,
                                         TextStream &signatureStream,
                                         const GeneratorContext &context)
 {
-    const auto metaClass = context.metaClass();
+    const auto &metaClass = context.metaClass();
     const QString className = chopType(cpythonTypeName(metaClass));
     const QString funcName = className + u"__copy__"_s;
 
-    signatureStream << fullPythonClassName(metaClass) << ".__copy__(self)->typing.Self\n";
+    // PYSIDE-3135 replace _Self by Self when the minimum Python version is 3.11
+    signatureStream << fullPythonClassName(metaClass) << ".__copy__(self)->typing._Self\n";
     definitionStream << PyMethodDefEntry{u"__copy__"_s, funcName, {"METH_NOARGS"_ba}, {}}
                      << ",\n";
 
@@ -5094,7 +5177,7 @@ void CppGenerator::writeRichCompareFunction(TextStream &s, TextStream &t,
     const QList<AbstractMetaFunctionCList> &groupedFuncs =
         filterGroupedOperatorFunctions(metaClass, OperatorQueryOption::ComparisonOp);
     for (const AbstractMetaFunctionCList &overloads : groupedFuncs) {
-        const auto rfunc = overloads[0];
+        const auto &rfunc = overloads.constFirst();
 
         const auto op = rfunc->comparisonOperatorType().value();
         s << "case " << AbstractMetaFunction::pythonRichCompareOpCode(op)
@@ -5249,7 +5332,7 @@ QString CppGenerator::signatureParameter(const AbstractMetaArgument &arg, bool i
     QTextStream s(&result);
 
     auto metaType = arg.type();
-    if (auto viewOn = metaType.viewOn())
+    if (const auto *viewOn = metaType.viewOn())
         metaType = *viewOn;
     s << arg.name() << ':';
 
@@ -5326,13 +5409,16 @@ void CppGenerator::writeSignatureInfo(TextStream &s, const OverloadData &overloa
             for (const auto &spec : metaClass->propertySpecs()) {
                 auto typeEntry = spec.typeEntry();
                 QString text;
-                if (typeEntry->isFlags()) {
+                if (typeEntry->isEnum()) {
+                    const auto ete = std::static_pointer_cast<const EnumTypeEntry>(typeEntry);
+                    text = ete->qualifiedTargetLangName();
+                } else if (typeEntry->isFlags()) {
                     const auto fte = std::static_pointer_cast<const FlagsTypeEntry>(typeEntry);
                     text = fte->originator()->qualifiedTargetLangName();
                 } else {
                     text = typeEntry->qualifiedCppName();
                 }
-                auto &inst = spec.type().instantiations();
+                const auto &inst = spec.type().instantiations();
                 if (!inst.isEmpty()) {
                     text += u'[';
                     for (qsizetype i = 0, size = inst.size(); i < size; ++i) {
@@ -5548,10 +5634,10 @@ void CppGenerator::writeSignalInitialization(TextStream &s, const AbstractMetaCl
             const QByteArray cppSig =
                 QMetaObject::normalizedType(qPrintable(metaType.cppSignature()));
             if ((origType != cppSig) && (!metaType.isFlags())) {
-                qCWarning(lcShiboken).noquote().nospace()
-                    << "Typedef used on signal " << metaClass->qualifiedCppName() << "::"
-                    << cppSignal->signature();
-                }
+                QString msg = "Typedef used on signal "_L1 + metaClass->qualifiedCppName()
+                              + "::"_L1 + cppSignal->signature();
+                ReportHandler::addGeneralMessage(msg);
+            }
         }
     }
 
@@ -5674,10 +5760,11 @@ static ComplexTypeEntryCList pyBaseTypeEntries(const AbstractMetaClassCPtr &meta
     for (auto base : baseClasses) {
         for (; base != nullptr; base = base->baseClass()) { // Find a type that is not disabled.
             const auto ct = base->typeEntry()->codeGeneration();
-            if (ct == TypeEntry::GenerateCode || ct == TypeEntry::GenerateForSubclass)
+            if (ct == TypeEntry::GenerateCode || ct == TypeEntry::GenerateForSubclass) {
+                result.append(base->typeEntry());
                 break;
+            }
         }
-        result.append(base->typeEntry());
     }
     return result;
 }
@@ -5790,9 +5877,9 @@ void CppGenerator::writeClassRegister(TextStream &s,
     // 8:wrapperflags
     QByteArrayList wrapperFlags;
     if (enc)
-        wrapperFlags.append(QByteArrayLiteral("Shiboken::ObjectType::WrapperFlags::InnerClass"));
+        wrapperFlags.append("Shiboken::ObjectType::WrapperFlags::InnerClass"_ba);
     if (metaClass->deleteInMainThread())
-        wrapperFlags.append(QByteArrayLiteral("Shiboken::ObjectType::WrapperFlags::DeleteInMainThread"));
+        wrapperFlags.append("Shiboken::ObjectType::WrapperFlags::DeleteInMainThread"_ba);
     if (classTypeEntry->isValue())
         wrapperFlags.append("Shiboken::ObjectType::WrapperFlags::Value"_ba);
     if (wrapperFlags.isEmpty())
@@ -6009,7 +6096,8 @@ void CppGenerator::writeInitQtMetaTypeFunctionBody(TextStream &s, const Generato
     }
 
     for (const AbstractMetaEnum &metaEnum : metaClass->enums()) {
-        if (!metaEnum.isPrivate() && !metaEnum.isAnonymous()) {
+        if (!metaEnum.isPrivate() && !metaEnum.isAnonymous()
+            && metaEnum.typeEntry()->qtMetaTypeRegistration() != TypeSystem::QtMetaTypeRegistration::Disabled) {
             for (const QString &name : std::as_const(nameVariants)) {
                 s << "qRegisterMetaType< " << m_gsp
                     << metaEnum.typeEntry()->qualifiedCppName() << " >(\""
@@ -6922,7 +7010,7 @@ void CppGenerator::writeHashFunction(TextStream &s, TextStream &t, const Generat
 void CppGenerator::writeDefaultSequenceMethods(TextStream &s,
                                                const GeneratorContext &context) const
 {
-    const auto metaClass = context.metaClass();
+    const auto &metaClass = context.metaClass();
     ErrorReturn errorReturn = ErrorReturn::Zero;
 
     // __len__
@@ -7001,7 +7089,7 @@ QString CppGenerator::writeReprFunction(TextStream &s,
                                         const GeneratorContext &context,
                                         uint indirections)
 {
-    const auto metaClass = context.metaClass();
+    const auto &metaClass = context.metaClass();
     QString funcName = writeReprFunctionHeader(s, context);
     writeCppSelfDefinition(s, context);
     s << R"(QBuffer buffer;
index dc4d4a96c97b56cbcf29c2deaf53998ccce3269f..0ae86dd37e79752391a4e5e3c12e52afefe0aa22 100644 (file)
@@ -8,9 +8,9 @@
 #include "include.h"
 #include "modifications_typedefs.h"
 
-#include <QtCore/QFlags>
-#include <QtCore/QSet>
-#include <QtCore/QHash>
+#include <QtCore/qflags.h>
+#include <QtCore/qset.h>
+#include <QtCore/qhash.h>
 
 #include <memory>
 #include <utility>
@@ -95,7 +95,8 @@ private:
     void writeVirtualMethodCppCall(TextStream &s, const AbstractMetaFunctionCPtr &func,
                                    const QString &funcName, const QList<CodeSnip> &snips,
                                    const AbstractMetaArgument *lastArg, const TypeEntryCPtr &retType,
-                                   const QString &returnStatement, bool hasGil) const;
+                                   const QString &returnStatement,
+                                   bool ownsGil, bool hasGilVar) const;
 
     static VirtualMethodReturn virtualMethodReturn(const ApiExtractorResult &api,
                                                    const AbstractMetaFunctionCPtr &func,
@@ -145,6 +146,8 @@ private:
     void writeConstructorWrapper(TextStream &s,
                                  const OverloadData &overloadData,
                                  const GeneratorContext &classContext) const;
+    static void writeConstructorDummy(TextStream &s, const GeneratorContext &classContext,
+                                      const char *errorMessageFunc);
     void writeMethodWrapper(TextStream &s, const OverloadData &overloadData,
                             const GeneratorContext &classContext) const;
     void writeMethodWrapper(TextStream &s, TextStream &definitionStream,
@@ -328,7 +331,7 @@ private:
 
     /// Writes a C++ to Python conversion function.
     void writeCppToPythonFunction(TextStream &s, const QString &code, const QString &sourceTypeName,
-                                  QString targetTypeName = QString()) const;
+                                  const QString &targetTypeName = {}) const;
     void writeCppToPythonFunction(TextStream &s, const CustomConversionPtr &customConversion) const;
     void writeCppToPythonFunction(TextStream &s, const AbstractMetaType &containerType) const;
     /// Main target type name of a container (for naming the functions).
index cffea253284c177027215973a83d550affacac98..8ff47315f9e83c3aa427192f05afac35df473c6c 100644 (file)
@@ -10,7 +10,7 @@
 #include "textstream.h"
 #include "typedatabase.h"
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 #include <algorithm>
 
index 6245b24a841ffa80339dbb7646bbbe6ca6d4e939..fec67659c66010181c2084611112e8aec53874c3 100644 (file)
@@ -14,7 +14,7 @@
 #include <overloaddata.h>
 #include <smartpointertypeentry.h>
 
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
 
 using namespace Qt::StringLiterals;
 
@@ -96,7 +96,7 @@ void CppGenerator::generateSmartPointerClass(TextStream &s,
                                              const GeneratorContext &classContext)
 {
     s.setLanguage(TextStream::Language::Cpp);
-    AbstractMetaClassCPtr metaClass = classContext.metaClass();
+    const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
     const auto typeEntry = std::static_pointer_cast<const SmartPointerTypeEntry>(metaClass->typeEntry());
     const bool hasPointeeClass = classContext.pointeeClass() != nullptr;
     const auto smartPointerType = typeEntry->smartPointerType();
@@ -321,7 +321,7 @@ void CppGenerator::writeSmartPointerRichCompareFunction(TextStream &s,
     static const char selfPointeeVar[] = "cppSelfPointee";
     static const char cppArg0PointeeVar[] = "cppArg0Pointee";
 
-    const auto metaClass = context.metaClass();
+    const auto &metaClass = context.metaClass();
     QString baseName = cpythonBaseName(metaClass);
     writeRichCompareFunctionHeader(s, baseName, context);
 
@@ -410,7 +410,7 @@ void CppGenerator::writeSmartPointerGetattroFunction(TextStream &s,
                                                      const BoolCastFunctionOptional &boolCast)
 {
     Q_ASSERT(context.forSmartPointer());
-    const auto metaClass = context.metaClass();
+    const auto &metaClass = context.metaClass();
     writeGetattroDefinition(s, metaClass);
     s << "PyObject *tmp = PyObject_GenericGetAttr(self, name);\n"
       << "if (tmp)\n" << indent << "return tmp;\n" << outdent
index f665b30ff318e48bde44fee5774956d8449406ce..3c4e6382b0919978780325e65692084ff8ba38b6 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef CTYPENAMES_H
 #define CTYPENAMES_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 constexpr auto boolT = QLatin1StringView("bool");
 constexpr auto intT = QLatin1StringView("int");
index e81ad0797ed7d9e3075a0784f9acfeca19bc1838..fd50ff5d9ffb843b1a421ea2ff3226de91d11f0e 100644 (file)
@@ -6,8 +6,8 @@
 #include <messages.h>
 #include <typesystem.h>
 
-#include <QtCore/QDebug>
-#include <QtCore/QSet>
+#include <QtCore/qdebug.h>
+#include <QtCore/qset.h>
 
 static bool isCppPrimitiveString(const AbstractMetaType &type)
 {
index 385ad0f6305d3eb7d6fa0de9e67f98e6646068a0..a5dee8087ec258469600ed0b05de7d31d6ecad3b 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef GENERATORARGUMENT_H
 #define GENERATORARGUMENT_H
 
-#include <QtCore/QFlags>
+#include <QtCore/qflags.h>
 #include <QtCore/qobjectdefs.h>
 
 QT_FORWARD_DECLARE_CLASS(QDebug)
index 9ce91e5990f37aa9447be128c46aebe9dc9e048d..011a4a3eaa5ad615d2ccb4bff7c73088bd2bcf75 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef GENERATORSTRINGS_H
 #define GENERATORSTRINGS_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 QString CPP_ARG_N(int i);
 QString CPP_ARG_REMOVED(int i);
index bf5ad29d03f061f46778b2c977d89bc1e4e5a6d4..b6491afeb41a78356e41b36715674ab3963fdfd3 100644 (file)
 #include <algorithm>
 #include <set>
 
-#include <QtCore/QDir>
-#include <QtCore/QTextStream>
-#include <QtCore/QVariant>
-#include <QtCore/QDebug>
+#include <QtCore/qdir.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qvariant.h>
+#include <QtCore/qdebug.h>
 
 using namespace Qt::StringLiterals;
 
+using FunctionSet = QSet<AbstractMetaFunctionCPtr>;
+
 struct IndexValue
 {
     QString name; // "SBK_..."
@@ -46,6 +48,22 @@ struct IndexValue
     QString comment;
 };
 
+// Returns the methods hidden from base classes
+static FunctionSet getHiddenOverloads(const AbstractMetaFunctionCPtr &func)
+{
+    FunctionSet result;
+    // Check if this method hide other methods in base classes
+    auto hiddenBy = [&func](const AbstractMetaFunctionCPtr &f) {
+        return f != func && !f->isConstructor() && !f->isPrivate() && !f->isVirtual()
+               && !f->isUserAdded() && !f->isAbstract() && !f->isStatic()
+               && f->name() == func->name();
+    };
+    const auto &functions = func->ownerClass()->functions();
+    std::copy_if(functions.cbegin(), functions.cend(),
+                 std::inserter(result, result.end()), hiddenBy);
+    return result;
+}
+
 TextStream &operator<<(TextStream &s, const IndexValue &iv)
 {
     s << "    " << AlignedField(iv.name, 56) << " = " << iv.value << ',';
@@ -97,10 +115,10 @@ void HeaderGenerator::writeCopyCtor(TextStream &s,
                                     const AbstractMetaClassCPtr &metaClass)
 {
     s << wrapperName(metaClass) << "(const " << metaClass->qualifiedCppName()
-      << "& self) : " << metaClass->qualifiedCppName() << "(self)\n{\n}\n\n";
+      << "& self) : " << metaClass->qualifiedCppName() << "(self)\n{\n}\n";
 }
 
-static void writeProtectedEnums(TextStream &s, const AbstractMetaClassCPtr &metaClass)
+static void writeProtectedEnumsHelper(TextStream &s, const AbstractMetaClassCPtr &metaClass)
 {
     const QString name = metaClass->qualifiedCppName();
     for (const auto &e : metaClass->enums()) {
@@ -123,19 +141,24 @@ void HeaderGenerator::generateSmartPointerClass(TextStream &s,
     doGenerateClass(s, classContext);
 }
 
+QString HeaderGenerator::headerGuard(const QString &className)
+{
+    return "SBK_"_L1 + getFilteredCppSignatureString(className.toUpper()) + "_H"_L1;
+}
+
 void HeaderGenerator::doGenerateClass(TextStream &s, const GeneratorContext &classContext) const
 {
-    const AbstractMetaClassCPtr metaClass = classContext.metaClass();
+    const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
 
     // write license comment
     s << licenseComment();
 
     QString wrapperName = classContext.effectiveClassName();
-    QString outerHeaderGuard = getFilteredCppSignatureString(wrapperName);
+    QString outerHeaderGuard = headerGuard(classContext.effectiveClassName());
 
     // Header
-    s << "#ifndef SBK_" << outerHeaderGuard << "_H\n";
-    s << "#define SBK_" << outerHeaderGuard << "_H\n\n";
+    s << "#ifndef " << outerHeaderGuard << '\n'
+      << "#define " << outerHeaderGuard << "\n\n";
 
     if (!avoidProtectedHack())
         s << protectedHackDefine;
@@ -148,14 +171,14 @@ void HeaderGenerator::doGenerateClass(TextStream &s, const GeneratorContext &cla
     if (classContext.useWrapper())
         writeWrapperClass(s, wrapperName, classContext);
 
-    s << "#endif // SBK_" << outerHeaderGuard << "_H\n\n";
+    s << "#endif // " << outerHeaderGuard << '\n';
 }
 
 void HeaderGenerator::writeWrapperClass(TextStream &s,
                                         const QString &wrapperName,
                                         const GeneratorContext &classContext) const
 {
-    const auto metaClass = classContext.metaClass();
+    const auto &metaClass = classContext.metaClass();
 
     if (avoidProtectedHack()) {
         const auto includeGroups = classIncludes(metaClass);
@@ -163,6 +186,8 @@ void HeaderGenerator::writeWrapperClass(TextStream &s,
             s << includeGroup;
     }
 
+    s << "namespace Shiboken { struct AutoDecRef; class GilState; }\n\n";
+
     if (usePySideExtensions() && isQObject(metaClass))
         s << "namespace PySide { class DynamicQMetaObject; }\n\n";
 
@@ -192,75 +217,41 @@ void HeaderGenerator::writeInheritedWrapperClassDeclaration(TextStream &s,
                                                             const GeneratorContext &classContext) const
 {
     const QString wrapperName = classContext.effectiveClassName();
-    const QString innerHeaderGuard =
-        getFilteredCppSignatureString(wrapperName).toUpper();
+    const QString innerHeaderGuard = headerGuard(wrapperName);
 
-    s << "#  ifndef SBK_" << innerHeaderGuard << "_H\n"
-      << "#  define SBK_" << innerHeaderGuard << "_H\n\n"
+    s << "#  ifndef " << innerHeaderGuard << '\n'
+      << "#  define " << innerHeaderGuard << "\n\n"
       << "// Inherited base class:\n";
 
     writeWrapperClassDeclaration(s, wrapperName, classContext);
 
-    s << "#  endif // SBK_" << innerHeaderGuard << "_H\n\n";
+    s << "#  endif // " << innerHeaderGuard << "\n\n";
 }
 
 void HeaderGenerator::writeWrapperClassDeclaration(TextStream &s,
                                                    const QString &wrapperName,
                                                    const GeneratorContext &classContext) const
 {
-    const AbstractMetaClassCPtr metaClass = classContext.metaClass();
+    const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
     const auto typeEntry = metaClass->typeEntry();
-    InheritedOverloadSet inheritedOverloads;
 
     // Class
     s << "class " << wrapperName
       << " : public " << metaClass->qualifiedCppName()
-      << "\n{\npublic:\n" << indent
-      << wrapperName << "(const " << wrapperName << " &) = delete;\n"
-      << wrapperName << "& operator=(const " << wrapperName << " &) = delete;\n"
-      << wrapperName << '(' << wrapperName << " &&) = delete;\n"
-      << wrapperName << "& operator=(" << wrapperName << " &&) = delete;\n\n";
-
-    // Make protected enums accessible
-    if (avoidProtectedHack()) {
-        recurseClassHierarchy(metaClass, [&s] (const AbstractMetaClassCPtr &metaClass) {
-            writeProtectedEnums(s, metaClass);
-            return false;
-        });
-    }
+      << "\n{\npublic:\n" << indent;
 
-    if (avoidProtectedHack() && metaClass->hasProtectedFields()) {
-        s << "\n// Make protected fields accessible\n";
-        const QString name = metaClass->qualifiedCppName();
-        for (const auto &f : metaClass->fields()) {
-            if (f.isProtected())
-                s << "using " << name << "::"  << f.originalName() << ";\n";
-        }
-        s << '\n';
-    }
+    writeProtectedEnums(s, classContext);
+    writeSpecialFunctions(s, wrapperName, classContext);
 
     int maxOverrides = 0;
     for (const auto &func : metaClass->functions()) {
         const auto generation = functionGeneration(func);
-        writeFunction(s, func, &inheritedOverloads, generation);
+        writeFunction(s, func, generation);
         // PYSIDE-803: Build a boolean cache for unused overrides.
         if (generation.testFlag(FunctionGenerationFlag::VirtualMethod))
             maxOverrides++;
     }
 
-    //destructor
-    // PYSIDE-504: When C++ 11 is used, then the destructor must always be declared.
-    if (!avoidProtectedHack() || !metaClass->hasPrivateDestructor()
-        || alwaysGenerateDestructorDeclaration()) {
-        if (avoidProtectedHack() && metaClass->hasPrivateDestructor())
-            s << "// C++11: need to declare (unimplemented) destructor because "
-                 "the base class destructor is private.\n";
-        s << '~' << wrapperName << "()";
-        if (metaClass->hasVirtualDestructor())
-            s << " override";
-        s << ";\n";
-    }
-
     writeClassCodeSnips(s, typeEntry->codeSnips(),
                         TypeSystem::CodeSnipPositionDeclaration, TypeSystem::NativeCode,
                         classContext);
@@ -273,12 +264,6 @@ void *qt_metacast(const char *_clname) override;
 )";
     }
 
-    if (!inheritedOverloads.isEmpty()) {
-        s << "// Inherited overloads, because the using keyword sux\n";
-        for (const auto &func : std::as_const(inheritedOverloads))
-            writeMemberFunctionWrapper(s, func);
-    }
-
     if (usePySideExtensions())
         s << "static void pysideInitQtMetaTypes();\n";
 
@@ -286,9 +271,11 @@ void *qt_metacast(const char *_clname) override;
     Q_ASSERT(maxOverrides > 0 || !needsMethodCache);
 
     if (needsMethodCache)
-        s << "void resetPyMethodCache();\n";
+        s << "\nvoid resetPyMethodCache();\n";
 
-    s << outdent << "private:\n" << indent;
+    writeProtectedFields(s, classContext);
+
+    s << outdent << "\nprivate:\n" << indent;
 
     if (!metaClass->userAddedPythonOverrides().isEmpty()) {
         for (const auto &f : metaClass->userAddedPythonOverrides())
@@ -306,6 +293,70 @@ void *qt_metacast(const char *_clname) override;
     s << outdent << "};\n\n";
 }
 
+void HeaderGenerator::writeSpecialFunctions(TextStream &s, const QString &wrapperName,
+                                            const GeneratorContext &classContext) const
+{
+    const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
+
+    const auto &wrapperConstructors = ShibokenGenerator::getWrapperConstructors(metaClass);
+    for (const auto &func : wrapperConstructors)
+        writeConstructor(s, func);
+    s << wrapperName << "(const " << wrapperName << " &) = delete;\n";
+    // Special inline copy CT (Wrapper from metaClass)
+    const auto &copyConstructors = metaClass->queryFunctions(FunctionQueryOption::CopyConstructor);
+    if (!copyConstructors.isEmpty()) {
+        auto generation = functionGeneration(copyConstructors.constFirst());
+        if (generation.testFlag(FunctionGenerationFlag::WrapperSpecialCopyConstructor))
+            writeCopyCtor(s, metaClass);
+    }
+    s << wrapperName << "& operator=(const " << wrapperName << " &) = delete;\n"
+      << wrapperName << '(' << wrapperName << " &&) = delete;\n"
+      << wrapperName << "& operator=(" << wrapperName << " &&) = delete;\n";
+
+    // destructor
+    // PYSIDE-504: When C++ 11 is used, then the destructor must always be declared.
+    if (!avoidProtectedHack() || !metaClass->hasPrivateDestructor()
+        || alwaysGenerateDestructorDeclaration()) {
+        if (avoidProtectedHack() && metaClass->hasPrivateDestructor())
+            s << "// C++11: need to declare (unimplemented) destructor because "
+                 "the base class destructor is private.\n";
+        s << '~' << wrapperName << "()";
+        if (metaClass->hasVirtualDestructor())
+            s << " override";
+        s << ";\n\n";
+    }
+}
+
+void HeaderGenerator::writeProtectedEnums(TextStream &s,
+                                          const GeneratorContext &classContext)
+{
+    // Make protected enums accessible
+    if (avoidProtectedHack()) {
+        const auto lastPos = s.pos();
+        const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
+        recurseClassHierarchy(metaClass, [&s] (const AbstractMetaClassCPtr &metaClass) {
+            writeProtectedEnumsHelper(s, metaClass);
+            return false;
+        });
+        if (s.pos() != lastPos)
+            s << '\n';
+    }
+}
+
+void HeaderGenerator::writeProtectedFields(TextStream &s,
+                                           const GeneratorContext &classContext)
+{
+    const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
+    if (avoidProtectedHack() && metaClass->hasProtectedFields()) {
+        s << "\n// Make protected fields accessible\n";
+        const QString name = metaClass->qualifiedCppName();
+        for (const auto &f : metaClass->fields()) {
+            if (f.isProtected())
+                s << "using " << name << "::"  << f.originalName() << ";\n";
+        }
+    }
+}
+
 // Write an inline wrapper around a function
 void HeaderGenerator::writeMemberFunctionWrapper(TextStream &s,
                                                  const AbstractMetaFunctionCPtr &func,
@@ -339,7 +390,7 @@ void HeaderGenerator::writeMemberFunctionWrapper(TextStream &s,
             enumTypeEntry = type.typeEntry();
         if (enumTypeEntry) {
             s << type.cppSignature() << '(' << arg.name() << ')';
-        } else if (type.passByValue() && type.isUniquePointer()) {
+        } else if (type.useStdMove()) {
             s << stdMove(arg.name());
         } else {
             s << arg.name();
@@ -348,51 +399,41 @@ void HeaderGenerator::writeMemberFunctionWrapper(TextStream &s,
     s << "); }\n";
 }
 
+void HeaderGenerator::writeConstructor(TextStream &s, const AbstractMetaFunctionCPtr &func) const
+{
+    Options option = func->hasSignatureModifications()
+    ? Generator::OriginalTypeDescription : Generator::NoOption;
+    s << functionSignature(func, {}, {}, option) << ";\n";
+}
+
 void HeaderGenerator::writeFunction(TextStream &s, const AbstractMetaFunctionCPtr &func,
-                                    InheritedOverloadSet *inheritedOverloads,
                                     FunctionGeneration generation) const
 {
-
-    // do not write copy ctors here.
-    if (generation.testFlag(FunctionGenerationFlag::WrapperSpecialCopyConstructor)) {
-        writeCopyCtor(s, func->ownerClass());
-        return;
-    }
-
     if (generation.testFlag(FunctionGenerationFlag::ProtectedWrapper))
         writeMemberFunctionWrapper(s, func, u"_protected"_s);
 
-    if (generation.testFlag(FunctionGenerationFlag::WrapperConstructor)) {
-        Options option =  func->hasSignatureModifications()
-            ? Generator::OriginalTypeDescription : Generator::NoOption;
-        s << functionSignature(func, {}, {}, option) << ";\n";
+    if (!generation.testFlag(FunctionGenerationFlag::VirtualMethod))
         return;
-    }
 
-    const bool isVirtual = generation.testFlag(FunctionGenerationFlag::VirtualMethod);
-    if (isVirtual) {
-        s << functionSignature(func, {}, {}, Generator::OriginalTypeDescription)
-            << " override;\n";
-    }
+    const Options options = Generator::OriginalTypeDescription;
+    s << functionSignature(func, {}, {}, options)
+        << " override;\n";
 
-    // Check if this method hide other methods in base classes
-    if (isVirtual) {
-        for (const auto &f : func->ownerClass()->functions()) {
-            if (f != func
-                && !f->isConstructor()
-                && !f->isPrivate()
-                && !f->isVirtual()
-                && !f->isAbstract()
-                && !f->isStatic()
-                && f->name() == func->name()) {
-                inheritedOverloads->insert(f);
-            }
-        }
+    if (!getReusedOverridenFunctions(func->ownerClass()).contains(func)) {
+        s << functionSignature(func, {}, {}, options | Generator::PythonOverrideImplementation)
+            << ";\n";
+    }
 
-        // TODO: when modified an abstract method ceases to be virtual but stays abstract
-        //if (func->isModifiedRemoved() && func->isAbstract()) {
-        //}
+    const auto &hiddenOverloads = getHiddenOverloads(func);
+    if (!hiddenOverloads.isEmpty()) {
+        s << "// Inherited overloads, because the using keyword sux\n";
+        for (const auto &func : hiddenOverloads)
+            writeMemberFunctionWrapper(s, func);
+        s << '\n';
     }
+
+    // TODO: when modified an abstract method ceases to be virtual but stays abstract
+    //if (func->isModifiedRemoved() && func->isAbstract()) {
 }
 
 // Find equivalent typedefs "using Foo=QList<int>", "using Bar=QList<int>"
index 814d012f124c3950f27dffcce0e6265db78562c7..a6bbdf06e7d23eb1559337ff9318d43de4ccdeb1 100644 (file)
@@ -8,8 +8,8 @@
 #include "include.h"
 #include "modifications_typedefs.h"
 
-#include <QtCore/QList>
-#include <QtCore/QSet>
+#include <QtCore/qlist.h>
+#include <QtCore/qset.h>
 
 struct IndexValue;
 class AbstractMetaFunction;
@@ -36,17 +36,16 @@ protected:
     bool finishGeneration() override;
 
 private:
-    using InheritedOverloadSet = QSet<AbstractMetaFunctionCPtr>;
     using IndexValues = QList<IndexValue>;
 
+    static QString headerGuard(const QString &className);
     void doGenerateClass(TextStream &s, const GeneratorContext &classContext) const;
     IndexValues collectTypeIndexes(const AbstractMetaClassCList &classList);
     IndexValues collectConverterIndexes() const;
 
     static void writeCopyCtor(TextStream &s, const AbstractMetaClassCPtr &metaClass);
-    void writeFunction(TextStream &s,
-                       const AbstractMetaFunctionCPtr &func,
-                       InheritedOverloadSet *inheritedOverloads,
+    void writeConstructor(TextStream &s, const AbstractMetaFunctionCPtr &func) const;
+    void writeFunction(TextStream &s, const AbstractMetaFunctionCPtr &func,
                        FunctionGeneration generation) const;
     static void writeSbkTypeFunction(TextStream &s, const AbstractMetaEnum &cppEnum);
     static void writeSbkTypeFunction(TextStream &s, const AbstractMetaClassCPtr &cppClass);
@@ -68,6 +67,12 @@ private:
     void writeWrapperClassDeclaration(TextStream &s,
                                       const QString &wrapperName,
                                       const GeneratorContext &classContext) const;
+    static void writeProtectedEnums(TextStream &s, const GeneratorContext &classContext);
+    void writeSpecialFunctions(TextStream &s, const QString &wrapperName,
+                               const GeneratorContext &classContext) const;
+    static void writeProtectedFields(TextStream &s,
+                                     const GeneratorContext &classContext);
+
     void writeWrapperClass(TextStream &s, const QString &wrapperName, const GeneratorContext &classContext) const;
     void writeInheritedWrapperClassDeclaration(TextStream &s,
                                                const GeneratorContext &classContext) const;
index a9e652c6d70b2f3a6688b6c8e50a5d49f770cd45..36a1df967567f029ac4e8da53413b8de32ef893f 100644 (file)
@@ -19,9 +19,9 @@
 
 #include "qtcompat.h"
 
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QTemporaryFile>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qtemporaryfile.h>
 
 #include <algorithm>
 #include <utility>
@@ -31,17 +31,11 @@ using namespace Qt::StringLiterals;
 static QString getTypeName(const AbstractMetaType &type)
 {
     TypeEntryCPtr typeEntry = type.typeEntry();
-    if (typeEntry->isPrimitive())
-        typeEntry = basicReferencedTypeEntry(typeEntry);
-    QString typeName = typeEntry->name();
+    QString typeName = type.basicPrimitiveName();
     if (typeEntry->isContainer()) {
         QStringList types;
-        for (const auto &cType : type.instantiations()) {
-            TypeEntryCPtr typeEntry = cType.typeEntry();
-            if (typeEntry->isPrimitive())
-                typeEntry = basicReferencedTypeEntry(typeEntry);
-            types << typeEntry->name();
-        }
+        for (const auto &cType : type.instantiations())
+            types << cType.basicPrimitiveName();
         typeName += u'<' + types.join(u',') + u" >"_s;
     }
     return typeName;
@@ -182,7 +176,8 @@ void OverloadDataRootNode::sortNextOverloads(const ApiExtractorResult &api)
             // and being PointF implicitly convertible from Point, an list<T> instantiation with T
             // as Point must come before the PointF instantiation, or else list<Point> will never
             // be called. In the case of primitive types, list<double> must come before list<int>.
-            if (instantiation.isPrimitive() && (signedIntegerPrimitives.contains(instantiation.name()))) {
+            if (instantiation.isPrimitive()
+                && signedIntegerPrimitives.contains(instantiation.basicPrimitiveName())) {
                 for (const QString &primitive : std::as_const(nonIntegerPrimitives))
                     graph.addNode(getImplicitConversionTypeName(ov->argType(), instantiation, nullptr, primitive));
             } else {
@@ -262,7 +257,8 @@ void OverloadDataRootNode::sortNextOverloads(const ApiExtractorResult &api)
                 if (!graph.containsEdge(targetTypeEntryName, convertible)) // Avoid cyclic dependency.
                     graph.addEdge(convertible, targetTypeEntryName);
 
-                if (instantiation.isPrimitive() && (signedIntegerPrimitives.contains(instantiation.name()))) {
+                if (instantiation.isPrimitive()
+                    && signedIntegerPrimitives.contains(instantiation.basicPrimitiveName())) {
                     for (const QString &primitive : std::as_const(nonIntegerPrimitives)) {
                         QString convertibleTypeName =
                             getImplicitConversionTypeName(ov->argType(), instantiation, nullptr, primitive);
index 48d83cdb7bcca61820842af5ddea0ac981b379a6..c9d271290a45d0a0ce5ca611ad564cda650887d8 100644 (file)
@@ -7,8 +7,8 @@
 #include <apiextractorresult.h>
 #include <abstractmetaargument.h>
 
-#include <QtCore/QBitArray>
-#include <QtCore/QList>
+#include <QtCore/qbitarray.h>
+#include <QtCore/qlist.h>
 
 #include <memory>
 
diff --git a/sources/shiboken6/generator/shiboken/overridecacheentry.cpp b/sources/shiboken6/generator/shiboken/overridecacheentry.cpp
new file mode 100644 (file)
index 0000000..2c244f9
--- /dev/null
@@ -0,0 +1,28 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "overridecacheentry.h"
+
+#include <abstractmetaargument.h>
+#include <abstractmetatype.h>
+
+OverrideCacheEntry::OverrideCacheEntry(const AbstractMetaFunctionCPtr &f) :
+    m_func(f), m_modifications(f->modifications())
+{
+    m_types.reserve(1 + m_func->arguments().size());
+    m_types.append(m_func->type());
+    for (const auto &arg: m_func->arguments())
+        m_types.append(arg.type());
+}
+
+bool OverrideCacheEntry::equals(const OverrideCacheEntry &rhs) const noexcept
+{
+    return m_types == rhs.m_types && m_modifications == rhs.m_modifications;
+}
+
+size_t OverrideCacheEntry::hashValue(size_t seed) const noexcept
+{
+    return qHashMulti(seed,
+                      qHashRange(m_types.cbegin(), m_types.cend(), seed),
+                      qHashRange(m_modifications.cbegin(), m_modifications.cend(), seed));
+}
diff --git a/sources/shiboken6/generator/shiboken/overridecacheentry.h b/sources/shiboken6/generator/shiboken/overridecacheentry.h
new file mode 100644 (file)
index 0000000..7547106
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef OVERRIDECACHEENTRY_H
+#define OVERRIDECACHEENTRY_H
+
+#include <abstractmetafunction.h>
+#include <modifications.h>
+
+#include <QtCore/qhash.h>
+
+// Cache a (virtual function) by types and modifications for reusing Python
+// override code.
+class OverrideCacheEntry
+{
+public:
+    explicit OverrideCacheEntry(const AbstractMetaFunctionCPtr &f);
+
+    const AbstractMetaFunctionCPtr &function() const { return m_func; }
+
+    size_t hashValue(size_t seed) const noexcept;
+
+private:
+    bool equals(const OverrideCacheEntry &rhs) const noexcept;
+
+    friend bool comparesEqual(const OverrideCacheEntry &lhs,
+                              const OverrideCacheEntry &rhs) noexcept
+    { return lhs.equals(rhs); }
+
+    Q_DECLARE_EQUALITY_COMPARABLE(OverrideCacheEntry)
+
+    AbstractMetaFunctionCPtr m_func;
+    QList<AbstractMetaType> m_types;
+    FunctionModificationList m_modifications;
+};
+
+inline size_t qHash(const OverrideCacheEntry &e, size_t seed = 0) noexcept
+{
+    return e.hashValue(seed);
+}
+
+#endif // OVERRIDECACHEENTRY_H
index 6c7658ff69619acc74dc869c5287c5144e69fcb5..be1a7cefcd660d224316188d45eff47556adb2aa 100644 (file)
@@ -4,7 +4,7 @@
 #ifndef PYTYPENAMES_H
 #define PYTYPENAMES_H
 
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
 
 constexpr auto pyBoolT = QLatin1StringView ("PyBool");
 constexpr auto pyFloatT = QLatin1StringView ("PyFloat");
index 68ee818005aa88810b36647e4ad7c78b32ac511b..def95e3fb3295fc0553d79718392cfdf37e5b60d 100644 (file)
@@ -16,6 +16,7 @@
 #include <abstractmetafunction.h>
 #include <abstractmetalang.h>
 #include <abstractmetalang_helpers.h>
+#include "overridecacheentry.h"
 #include <usingmember.h>
 #include <exception.h>
 #include <messages.h>
 
 #include "qtcompat.h"
 
-#include <QtCore/QDir>
-#include <QtCore/QDebug>
-#include <QtCore/QRegularExpression>
+#include <QtCore/qdir.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qset.h>
 
 #include <algorithm>
 #include <limits>
@@ -76,6 +78,8 @@ QString CPP_ARG_REMOVED(int i)
     return CPP_ARG_REMOVED_PREFIX + QString::number(i);
 }
 
+static QSet<OverrideCacheEntry> pythonOverrideCache;
+
 const char *const METHOD_DEF_SENTINEL = "{nullptr, nullptr, 0, nullptr} // Sentinel\n";
 const char *const PYTHON_TO_CPPCONVERSION_STRUCT = "Shiboken::Conversions::PythonToCppConversion";
 
@@ -105,9 +109,13 @@ struct ShibokenGeneratorOptions
 struct GeneratorClassInfoCacheEntry
 {
     ShibokenGenerator::FunctionGroups functionGroups;
+    AbstractMetaFunctionCList constructors;
+    AbstractMetaFunctionCList wrapperConstructors;
     QList<AbstractMetaFunctionCList> numberProtocolOperators;
     BoolCastFunctionOptional boolCastFunctionO;
     ShibokenGenerator::AttroCheck attroCheck;
+    // Maps a virtual function to an equivalent one for resuing the override implementation
+    ShibokenGenerator::FunctionMapping reusedOverrides;
 };
 
 using GeneratorClassInfoCache = QHash<AbstractMetaClassCPtr, GeneratorClassInfoCacheEntry>;
@@ -339,6 +347,13 @@ QString ShibokenGenerator::fullPythonClassName(const AbstractMetaClassCPtr &meta
     return fullClassName;
 }
 
+// Non-smartpointer classes only (see comment at fileNameForContextHelper)
+QString ShibokenGenerator::headerFileNameForClass(const AbstractMetaClassCPtr &metaClass)
+{
+    Q_ASSERT(!metaClass->typeEntry()->isSmartPointer());
+    return fileNameForClassHelper(metaClass, u"_wrapper.h"_s);
+}
+
 QString ShibokenGenerator::headerFileNameForContext(const GeneratorContext &context)
 {
     return fileNameForContextHelper(context, u"_wrapper.h"_s);
@@ -399,6 +414,20 @@ QString ShibokenGenerator::fullPythonFunctionName(const AbstractMetaFunctionCPtr
     return funcName;
 }
 
+// Return name of the static function implementing a python override ("sbk_o_..")
+QString ShibokenGenerator::pythonOverrideImplName(const AbstractMetaFunctionCPtr &func)
+{
+    QString result = "sbk_o_"_L1;
+    if (func->isOperatorOverload()) {
+        QString name = pythonOperatorFunctionName(func);
+        name.remove(u'_');
+        result += "op_"_L1 + name;
+    } else {
+        result += func->originalName();
+    }
+    return result;
+}
+
 bool ShibokenGenerator::wrapperDiagnostics()
 {
     return m_options.wrapperDiagnostics;
@@ -985,11 +1014,8 @@ QString ShibokenGenerator::cpythonIsConvertibleFunction(const TypeEntryCPtr &typ
 {
     if (type->isWrapperType()) {
         QString result = u"Shiboken::Conversions::"_s;
-        bool isValue = false;
-        if (type->isValue()) {
-            const auto cte = std::static_pointer_cast<const ComplexTypeEntry>(type);
-            isValue = !cte->isValueTypeWithCopyConstructorOnly();
-        }
+        const bool isValue = type->isValue()
+            && std::static_pointer_cast<const CppTypeEntry>(type)->isDefaultConstructible();
         result += isValue ? u"isPythonToCppValueConvertible"_s
                           : u"isPythonToCppPointerConvertible"_s;
         result += u"("_s + cpythonTypeNameExt(type) + u", "_s;
@@ -1163,6 +1189,13 @@ void ShibokenGenerator::writeFunctionArguments(TextStream &s,
         s << "Shiboken::GilState &gil, PyObject *" << PYTHON_OVERRIDE_VAR;
         argUsed += 2;
     }
+
+    if (options.testFlag(PythonOverrideImplementation)) {
+        s << "const char *ownerClassName, const char *funcName, Shiboken::GilState &gil, const Shiboken::AutoDecRef &"
+            << PYTHON_OVERRIDE_VAR;
+        argUsed += 3;
+    }
+
     for (const auto &arg : func->arguments()) {
         if (options.testFlag(Generator::SkipRemovedArguments) && arg.isModifiedRemoved())
             continue;
@@ -1192,14 +1225,16 @@ QString ShibokenGenerator::functionReturnType(const AbstractMetaFunctionCPtr &fu
 }
 
 QString ShibokenGenerator::functionSignature(const AbstractMetaFunctionCPtr &func,
-                                             const QString &prepend,
+                                             const QString &className,
                                              const QString &append,
                                              Options options,
                                              int /* argCount */) const
 {
     StringStream s(TextStream::Language::Cpp);
     // The actual function
-    if (!options.testFlag(Option::SkipDefaultValues) && func->isStatic()) // Declaration
+    const bool isDeclaration = !options.testFlag(Option::SkipDefaultValues);
+    const bool isStaticOverride = options.testFlag(Option::PythonOverrideImplementation);
+    if (isDeclaration && (isStaticOverride || func->isStatic()))
         s << "static ";
     if (func->isEmptyFunction() || func->needsReturnType())
         s << functionReturnType(func, options) << ' ';
@@ -1207,15 +1242,17 @@ QString ShibokenGenerator::functionSignature(const AbstractMetaFunctionCPtr &fun
         options |= Generator::SkipReturnType;
 
     // name
-    QString name(func->originalName());
+    QString name = isStaticOverride ? pythonOverrideImplName(func) : func->originalName();
     if (func->isConstructor())
         name = wrapperName(func->ownerClass());
 
-    s << prepend << name << append << '(';
+    if (!isDeclaration && !className.isEmpty())
+        s << className << "::";
+    s << name << append << '(';
     writeFunctionArguments(s, func, options);
     s << ')';
 
-    if (func->isConstant())
+    if (func->isConstant() && !isStaticOverride)
         s << " const";
 
     if (func->exceptionSpecification() == ExceptionSpecification::NoExcept)
@@ -1238,7 +1275,7 @@ void ShibokenGenerator::writeArgumentNames(TextStream &s,
         if (argCount > 0)
             s << ", ";
         const bool isVirtualCall = options.testFlag(Option::VirtualCall);
-        const bool useStdMove = isVirtualCall && type.isUniquePointer() && type.passByValue();
+        const bool useStdMove = isVirtualCall && type.useStdMove();
         s << (useStdMove ? stdMove(argument.name()) : argument.name());
 
         if (!isVirtualCall
@@ -1337,7 +1374,7 @@ QString ShibokenGenerator::getCodeSnippets(const CodeSnipList &codeSnips,
 
 void ShibokenGenerator::processClassCodeSnip(QString &code, const GeneratorContext &context) const
 {
-    auto metaClass = context.metaClass();
+    const auto &metaClass = context.metaClass();
     // Replace template variable by the Python Type object
     // for the class context in which the variable is used.
     code.replace(u"%PYTHONTYPEOBJECT"_s,
@@ -1878,8 +1915,8 @@ ShibokenGenerator::AttroCheck
     AttroCheck result;
     if (classNeedsGetattroOverloadFunctionImpl(functionGroups))
         result |= AttroCheckFlag::GetattroOverloads;
-    if (metaClass->queryFirstFunction(metaClass->functions(),
-                                      FunctionQueryOption::GetAttroFunction)) {
+    if (AbstractMetaClass::queryFirstFunction(metaClass->functions(),
+                                              FunctionQueryOption::GetAttroFunction)) {
         result |= AttroCheckFlag::GetattroUser;
     }
     if (usePySideExtensions() && metaClass->qualifiedCppName() == qObjectT)
@@ -1889,8 +1926,8 @@ ShibokenGenerator::AttroCheck
         if (std::any_of(funcs.cbegin(), funcs.cend(), isVirtualOverride))
             result |= AttroCheckFlag::SetattroMethodOverride;
     }
-    if (metaClass->queryFirstFunction(metaClass->functions(),
-                                      FunctionQueryOption::SetAttroFunction)) {
+    if (AbstractMetaClass::queryFirstFunction(metaClass->functions(),
+                                              FunctionQueryOption::SetAttroFunction)) {
         result |= AttroCheckFlag::SetattroUser;
     }
     // PYSIDE-1255: If setattro is generated for a class inheriting
@@ -1937,7 +1974,7 @@ AbstractMetaFunctionCList
                 if (func->isAssignmentOperator() || func->isConversionOperator()
                     || func->isModifiedRemoved()
                     || func->isPrivate() || func->ownerClass() != func->implementingClass()
-                    || func->isConstructor() || func->isOperatorOverload())
+                    || func->isOperatorOverload())
                     continue;
                 overloads.append(func);
             }
@@ -2025,6 +2062,7 @@ static bool isGroupable(const AbstractMetaFunctionCPtr &func)
     case AbstractMetaFunction::SetAttroFunction:
     case AbstractMetaFunction::ArrowOperator: // weird operator overloads
     case AbstractMetaFunction::SubscriptOperator:
+    case AbstractMetaFunction::OtherAssignmentOperatorFunction:
         return false;
     default:
         break;
@@ -2052,6 +2090,72 @@ ShibokenGenerator::FunctionGroups ShibokenGenerator::getGlobalFunctionGroups() c
     return results;
 }
 
+AbstractMetaFunctionCList
+   ShibokenGenerator::wrapperConstructorsImpl(const AbstractMetaClassCPtr &scope)
+{
+    auto pred = [](const AbstractMetaFunctionCPtr &f) {
+        return ShibokenGenerator::functionGeneration(f).testFlag(FunctionGenerationFlag::WrapperConstructor);
+    };
+    AbstractMetaFunctionCList result;
+    std::copy_if(scope->functions().cbegin(), scope->functions().cend(),
+                 std::back_inserter(result), pred);
+    return result;
+}
+
+// Check whether constructors are imported via "using" directive from a base class
+// (simple case of single inheritance)?
+static bool checkConstructorsFromUsingDirective(const AbstractMetaClassCPtr &scope)
+{
+    if (scope->baseClasses().size() != 1)
+        return false;
+    const auto base = scope->baseClass();
+    // base class name == constructors name
+    return scope->isUsingMember(base, base->name(), Access::Public);
+}
+
+// Clone base constructors imported via "using" directive for use in a derived class
+static AbstractMetaFunctionCList
+    cloneUsingConstructors(const AbstractMetaClassCPtr &scope,
+                           const AbstractMetaFunctionCList &baseCts)
+{
+    AbstractMetaFunctionCList result;
+    result.reserve(baseCts.size());
+    auto transform = [scope] (const AbstractMetaFunctionCPtr &baseCt) {
+        auto *clone = baseCt->copy();
+        clone->setOriginalName(scope->name());
+        clone->setName(scope->name());
+        clone->setOwnerClass(scope);
+        return AbstractMetaFunctionCPtr(clone);
+    };
+    std::transform(baseCts.cbegin(), baseCts.cend(),
+                   std::back_inserter(result), transform);
+    return result;
+}
+
+// Get constructors imported via "using" directive from a base class.
+static AbstractMetaFunctionCList
+    getConstructorsFromUsingDirective(const AbstractMetaClassCPtr &scope)
+{
+    static constexpr auto query = FunctionQueryOption::Constructors
+        | FunctionQueryOption::Visible | FunctionQueryOption::ClassImplements
+        | FunctionQueryOption::NotRemoved;
+    return cloneUsingConstructors(scope, scope->baseClass()->queryFunctions(query));
+}
+
+// Get wrapper constructors imported via "using" directive from a base class
+// (looser criterion).
+AbstractMetaFunctionCList
+    ShibokenGenerator::getWrapperConstructorsFromUsingDirective(const AbstractMetaClassCPtr &scope)
+{
+    AbstractMetaFunctionCList baseCts =
+        scope->baseClass()->queryFunctions(FunctionQueryOption::Constructors);
+    auto pred = [] (const AbstractMetaFunctionCPtr &c) {
+        return !ShibokenGenerator::functionGeneration(c).testFlag(FunctionGenerationFlag::WrapperConstructor);
+    };
+    baseCts.erase(std::remove_if(baseCts.begin(), baseCts.end(), pred), baseCts.end());
+    return cloneUsingConstructors(scope, baseCts);
+}
+
 const GeneratorClassInfoCacheEntry &
     ShibokenGenerator::getGeneratorClassInfo(const AbstractMetaClassCPtr &scope)
 {
@@ -2060,10 +2164,19 @@ const GeneratorClassInfoCacheEntry &
     if (it == cache->end()) {
         it = cache->insert(scope, {});
         auto &entry = it.value();
-        entry.functionGroups = getFunctionGroupsImpl(scope);
+        entry.functionGroups = getFunctionGroupsImpl(scope, &entry.constructors);
+        const bool useWrapper = shouldGenerateCppWrapper(scope);
+        if (useWrapper)
+            entry.wrapperConstructors = wrapperConstructorsImpl(scope);
+        if (entry.constructors.isEmpty() && checkConstructorsFromUsingDirective(scope)) {
+            entry.constructors.append(getConstructorsFromUsingDirective(scope));
+            entry.wrapperConstructors.append(getWrapperConstructorsFromUsingDirective(scope));
+        }
         entry.attroCheck = checkAttroFunctionNeedsImpl(scope, entry.functionGroups);
         entry.numberProtocolOperators = getNumberProtocolOperators(scope);
         entry.boolCastFunctionO = getBoolCast(scope);
+        if (shouldGenerateCppWrapper(scope)) // Skip final classes
+            entry.reusedOverrides = getReusedOverridesImpl(scope);
     }
     return it.value();
 }
@@ -2075,6 +2188,26 @@ ShibokenGenerator::FunctionGroups
     return getGeneratorClassInfo(scope).functionGroups;
 }
 
+AbstractMetaFunctionCList
+   ShibokenGenerator::getConstructors(const AbstractMetaClassCPtr &scope)
+{
+    Q_ASSERT(scope);
+    return getGeneratorClassInfo(scope).constructors;
+}
+
+AbstractMetaFunctionCList ShibokenGenerator::getWrapperConstructors(const AbstractMetaClassCPtr &scope)
+{
+    Q_ASSERT(scope);
+    return getGeneratorClassInfo(scope).wrapperConstructors;
+}
+
+const ShibokenGenerator::FunctionMapping &
+    ShibokenGenerator::getReusedOverridenFunctions(const AbstractMetaClassCPtr &scope)
+{
+    Q_ASSERT(scope);
+    return getGeneratorClassInfo(scope).reusedOverrides;
+}
+
 QList<AbstractMetaFunctionCList>
     ShibokenGenerator::numberProtocolOperators(const AbstractMetaClassCPtr &scope)
 {
@@ -2112,17 +2245,128 @@ static void removeConstOverloads(AbstractMetaFunctionCList *overloads)
     }
 }
 
+// For a list of overloads of the same argument count, return a list of functions
+// that can be removed by the type system overload removal rules.
+static AbstractMetaFunctionCList filterFunctions(const OverloadRemovalRules &removalRules,
+                                                 const AbstractMetaFunctionCList &overloads)
+{
+    const auto size = overloads.size();
+    Q_ASSERT(size > 1);
+    AbstractMetaFunctionCList result;
+
+    // Basic parameters that need to be equivalent
+    static constexpr AbstractMetaFunction::CompareResult expected =
+        AbstractMetaFunction::EqualName | AbstractMetaFunction::EqualVirtual
+        | AbstractMetaFunction::EqualConst | AbstractMetaFunction::EqualStatic
+        | AbstractMetaFunction::EqualReturnType;
+
+
+    // Find the varying argument and check if otherwise equivalent
+    AbstractMetaFunction::CompareResult differingArgMask{};
+    for (qsizetype a = 1; a < size; ++a) {
+        auto cr = overloads.constFirst()->compareTo(overloads.at(a).get());
+        if ((cr & expected) != expected)
+            return result;
+        auto argMask = cr & AbstractMetaFunction::Differ4ArgumentsMask;
+        if (a == 1)
+            differingArgMask = argMask;
+        else if (differingArgMask != argMask)
+            return result;
+    }
+
+    // Turn bit mask into argument number and check if only one argument differs
+    qsizetype argNo = -1;
+    if (differingArgMask == AbstractMetaFunction::DifferArgument1)
+        argNo = 0;
+    else if (differingArgMask == AbstractMetaFunction::DifferArgument2)
+        argNo = 1;
+    else if (differingArgMask == AbstractMetaFunction::DifferArgument3)
+        argNo = 2;
+    else if (differingArgMask == AbstractMetaFunction::DifferArgument4)
+        argNo = 3;
+    if (argNo < 0) // Several arguments differ
+        return result;
+
+    // Retrieve list of types of the varying argument
+    // FIXME PYSIDE-7: Refactor using C++ 20 views
+    QStringList types;
+    types.reserve(size);
+    for (const auto &f : overloads) {
+        auto amt = f->arguments().at(argNo).type();
+        if (!amt.passByValue() && !amt.passByConstRef()) // Only simple types so far
+            return result;
+        types.append(amt.basicPrimitiveName());
+    }
+
+    // Apply rules and compile list of redundant functions
+    for (const auto &rule : removalRules) {
+        if (const auto index = types.indexOf(rule.type); index != -1) {
+            for (const auto &redundantType : rule.redundantTypes) {
+                if (const auto index2 = types.indexOf(redundantType); index2 != -1) {
+                    auto redundant = overloads.at(index2);
+                    if (!result.contains(redundant)) { // nested long->int->short rule?
+                        ReportHandler::addGeneralMessage(msgRemoveRedundantOverload(redundant, rule.type));
+                        result.append(redundant);
+                    }
+                }
+            }
+        }
+    }
+    return result;
+}
+
+static bool argCountLessThan(const AbstractMetaFunctionCPtr &f1, const AbstractMetaFunctionCPtr &f2)
+{
+    return f1->arguments().size() < f2->arguments().size();
+}
+
+// For a list of overloads of the same name, remove functions with redundant arguments.
+// as defined by the type system overload removal rules. It is important that the order
+// is preserved, else topological sorting in OverloadData will go haywire.
+static void filterAllFunctions(const OverloadRemovalRules &removalRules,
+                               AbstractMetaFunctionCList *overloads)
+{
+    if (overloads->size() < 2)
+        return;
+    const auto maxArgsIt = std::max_element(overloads->cbegin(), overloads->cend(), argCountLessThan);
+    const auto maxArgs = (*maxArgsIt)->arguments().size();
+    if (maxArgs == 0)
+        return;
+
+    // FIXME PYSIDE-7: Refactor using C++ 20 views
+    for (qsizetype ac = 0; ac <= maxArgs; ++ac) {
+        // Check on lists of the same argument count.
+        AbstractMetaFunctionCList list;
+        auto sameArgumentCount = [ac](const AbstractMetaFunctionCPtr &f) {
+            return f->arguments().size() == ac; };
+        std::copy_if(overloads->cbegin(), overloads->cend(), std::back_inserter(list),
+                     sameArgumentCount);
+        if (list.size() > 1) {
+            const auto redundant = filterFunctions(removalRules, list);
+            for (const auto &r : redundant)
+                overloads->removeAll(r);
+        }
+    }
+}
+
 ShibokenGenerator::FunctionGroups
-    ShibokenGenerator::getFunctionGroupsImpl(const AbstractMetaClassCPtr &scope)
+    ShibokenGenerator::getFunctionGroupsImpl(const AbstractMetaClassCPtr &scope,
+                                            AbstractMetaFunctionCList *constructors)
 {
     AbstractMetaFunctionCList lst = scope->functions();
     scope->getFunctionsFromInvisibleNamespacesToBeGenerated(&lst);
+    const OverloadRemovalRules &removalRules = TypeDatabase::instance()->overloadRemovalRules();
 
     FunctionGroups results;
-    for (const auto &func : lst) {
+    for (const auto &func : std::as_const(lst)) {
         if (isGroupable(func)
             && func->ownerClass() == func->implementingClass()
             && func->generateBinding()) {
+            if (func->isConstructor()) {
+                if (func->functionType() != AbstractMetaFunction::MoveConstructorFunction)
+                    constructors->append(func);
+                continue;
+            }
             auto it = results.find(func->name());
             if (it == results.end()) {
                 it = results.insert(func->name(), AbstractMetaFunctionCList(1, func));
@@ -2139,11 +2383,31 @@ ShibokenGenerator::FunctionGroups
             }
             getInheritedOverloads(scope, &it.value());
             removeConstOverloads(&it.value());
+            if (!removalRules.isEmpty())
+                filterAllFunctions(removalRules, &it.value());
         }
     }
     return results;
 }
 
+ShibokenGenerator::FunctionMapping
+    ShibokenGenerator::getReusedOverridesImpl(const AbstractMetaClassCPtr &metaClass)
+{
+    ShibokenGenerator::FunctionMapping result;
+    for (const auto &func : metaClass->functions()) {
+        const auto generation = functionGeneration(func);
+        if (generation.testFlag(FunctionGenerationFlag::VirtualMethod)) {
+            OverrideCacheEntry fce(func);
+            auto it = pythonOverrideCache.constFind(fce);
+            if (it == pythonOverrideCache.cend())
+                pythonOverrideCache.insert(fce);
+            else
+                result.insert(func, it->function());
+        }
+    }
+    return result;
+}
+
 static bool removeNumberProtocolOperator(const AbstractMetaFunctionCPtr &f)
 {
     return !f->generateBinding()
index bd6f7afa9b1403b18d8d9801ba4d2b6f333ea8a4..a019e02bedf76eb977ecbdb44c784aa33ce83f65 100644 (file)
@@ -11,7 +11,8 @@
 #include "typesystem_typedefs.h"
 #include "typesystem_enums.h"
 
-#include <QtCore/QRegularExpression>
+#include <QtCore/qhash.h>
+#include <QtCore/qregularexpression.h>
 
 #include <array>
 #include <optional>
@@ -82,6 +83,7 @@ public:
     };
     Q_DECLARE_FLAGS(AttroCheck, AttroCheckFlag);
 
+    using FunctionMapping = QHash<AbstractMetaFunctionCPtr, AbstractMetaFunctionCPtr>;
     using FunctionGroups = QMap<QString, AbstractMetaFunctionCList>; // Sorted
 
     ShibokenGenerator();
@@ -109,6 +111,15 @@ protected:
      */
     FunctionGroups getGlobalFunctionGroups() const;
     static FunctionGroups getFunctionGroups(const AbstractMetaClassCPtr &scope);
+    /// Returns the constructors for which bindings should be generated.
+    /// \param scope Where to search for functions
+    static AbstractMetaFunctionCList getConstructors(const AbstractMetaClassCPtr &scope);
+    /// Returns the constructors which should be added to wrapper class.
+    /// \param scope Where to search for functions
+    static AbstractMetaFunctionCList getWrapperConstructors(const AbstractMetaClassCPtr &scope);
+    /// Returns mapping of virtual functions whose override handling code can be re-used.
+    static const FunctionMapping &
+        getReusedOverridenFunctions(const AbstractMetaClassCPtr &scope);
 
     static QList<AbstractMetaFunctionCList>
         numberProtocolOperators(const AbstractMetaClassCPtr &scope);
@@ -165,8 +176,8 @@ protected:
      *   \param arg_count the number of function arguments
      */
     QString functionSignature(const AbstractMetaFunctionCPtr &func,
-                              const QString &prepend = QString(),
-                              const QString &append = QString(),
+                              const QString &className = {},
+                              const QString &append = {},
                               Options options = NoOption,
                               int arg_count = -1) const;
 
@@ -215,10 +226,12 @@ protected:
 
     static QString fullPythonClassName(const AbstractMetaClassCPtr &metaClass);
 
+    static QString headerFileNameForClass(const AbstractMetaClassCPtr &metaClass);
     static QString headerFileNameForContext(const GeneratorContext &context);
     IncludeGroup baseWrapperIncludes(const GeneratorContext &classContext) const;
 
     static QString fullPythonFunctionName(const AbstractMetaFunctionCPtr &func, bool forceFunc);
+    static QString pythonOverrideImplName(const AbstractMetaFunctionCPtr &func);
 
     static bool wrapperDiagnostics();
 
@@ -370,13 +383,17 @@ private:
 
     static const GeneratorClassInfoCacheEntry &
         getGeneratorClassInfo(const AbstractMetaClassCPtr &scope);
-    static FunctionGroups getFunctionGroupsImpl(const AbstractMetaClassCPtr &scope);
+    static FunctionGroups getFunctionGroupsImpl(const AbstractMetaClassCPtr &scope,
+                                                AbstractMetaFunctionCList *constructors);
+    static AbstractMetaFunctionCList wrapperConstructorsImpl(const AbstractMetaClassCPtr &scope);
+    static AbstractMetaFunctionCList getWrapperConstructorsFromUsingDirective(const AbstractMetaClassCPtr &scope);
     static QList<AbstractMetaFunctionCList>
         getNumberProtocolOperators(const AbstractMetaClassCPtr &metaClass);
     static BoolCastFunctionOptional getBoolCast(const AbstractMetaClassCPtr &metaClass);
     static bool classNeedsGetattroOverloadFunctionImpl(const FunctionGroups &functionGroups);
     static AttroCheck checkAttroFunctionNeedsImpl(const AbstractMetaClassCPtr &metaClass,
                                                   const FunctionGroups &functionGroups);
+    static FunctionMapping getReusedOverridesImpl(const AbstractMetaClassCPtr &metaClass);
     static bool isVirtualOverride(const AbstractMetaFunctionCPtr &f);
 
     QString translateTypeForWrapperMethod(const AbstractMetaType &cType,
@@ -441,22 +458,22 @@ private:
                                             QString &code) const;
 
     /// Replaces the %CONVERTTOPYTHON type system variable.
-    inline void replaceConvertToPythonTypeSystemVariable(QString &code) const
+    void replaceConvertToPythonTypeSystemVariable(QString &code) const
     {
         replaceConverterTypeSystemVariable(TypeSystemToPythonFunction, code);
     }
     /// Replaces the %CONVERTTOCPP type system variable.
-    inline void replaceConvertToCppTypeSystemVariable(QString &code) const
+    void replaceConvertToCppTypeSystemVariable(QString &code) const
     {
         replaceConverterTypeSystemVariable(TypeSystemToCppFunction, code);
     }
     /// Replaces the %ISCONVERTIBLE type system variable.
-    inline void replaceIsConvertibleToCppTypeSystemVariable(QString &code) const
+    void replaceIsConvertibleToCppTypeSystemVariable(QString &code) const
     {
         replaceConverterTypeSystemVariable(TypeSystemIsConvertibleFunction, code);
     }
     /// Replaces the %CHECKTYPE type system variable.
-    inline void replaceTypeCheckTypeSystemVariable(QString &code) const
+    void replaceTypeCheckTypeSystemVariable(QString &code) const
     {
         replaceConverterTypeSystemVariable(TypeSystemCheckFunction, code);
     }
index 14e38942885a264c21e74d6422cf6ab6bff9c898..9bf165194c57e4b12e03ddc3266947f246554783 100644 (file)
@@ -68,6 +68,7 @@ helper.cpp helper.h
 pep384impl.cpp pep384impl.h
 pyobjectholder.h
 sbkarrayconverter.cpp sbkarrayconverter.h sbkarrayconverter_p.h
+sbkbindingutils.cpp sbkbindingutils.h
 sbkcontainer.cpp sbkcontainer.h
 sbkconverter.cpp sbkconverter.h sbkconverter_p.h
 sbkcppstring.cpp sbkcppstring.h sbkcpptonumpy.h
@@ -131,7 +132,7 @@ shiboken_compute_python_includes()
 shiboken_compute_python_libraries()
 
 if(PYTHON_LIMITED_API)
-    target_compile_definitions(libshiboken PUBLIC "-DPy_LIMITED_API=0x03080000")
+    target_compile_definitions(libshiboken PUBLIC "-DPy_LIMITED_API=0x03090000")
 endif()
 
 if(CMAKE_BUILD_TYPE STREQUAL "Debug")
@@ -161,6 +162,7 @@ install(FILES
         helper.h
         pyobjectholder.h
         sbkarrayconverter.h
+        sbkbindingutils.h
         sbkcontainer.h
         sbkconverter.h
         sbkcpptonumpy.h
index 62a8584e1b8ca60cbd0d1092b22e06a005ca99ef..12af8f5ca6fa31364d1685e6d9983cbbfdcf8885 100644 (file)
@@ -47,12 +47,8 @@ public:
     /// Returns the pointer of the Python object being held.
     [[nodiscard]] PyObject *object() const { return m_pyObj; }
     [[nodiscard]] operator PyObject *() const { return m_pyObj; }
-#ifndef Py_LIMITED_API
-    [[deprecated]] inline operator PyTupleObject *()
-    { return reinterpret_cast<PyTupleObject *>(m_pyObj); }
-#endif
-    inline operator bool() const { return m_pyObj != nullptr; }
-    inline PyObject *operator->() { return m_pyObj; }
+    operator bool() const { return m_pyObj != nullptr; }
+    PyObject *operator->() { return m_pyObj; }
 
     template<typename T>
     [[deprecated]] T cast()
index 61110618951d9755f56db18e7262e6f18216f1b7..4bae93b079d18f5a20e4deb8cbdcf900651a692d 100644 (file)
@@ -1,34 +1,32 @@
 // Copyright (C) 2019 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
+#include "autodecref.h"
 #include "basewrapper.h"
 #include "basewrapper_p.h"
 #include "bindingmanager.h"
+#include "gilstate.h"
 #include "helper.h"
 #include "pep384ext.h"
 #include "sbkconverter.h"
 #include "sbkerrors.h"
 #include "sbkfeature_base.h"
-#include "sbkstring.h"
 #include "sbkstaticstrings.h"
 #include "sbkstaticstrings_p.h"
+#include "sbkstring.h"
 #include "sbktypefactory.h"
-#include "autodecref.h"
-#include "gilstate.h"
-#include <string>
-#include <cstring>
-#include <cstddef>
-#include <set>
-#include <sstream>
-#include <algorithm>
-#include "threadstatesaver.h"
 #include "signature.h"
 #include "signature_p.h"
+#include "threadstatesaver.h"
 #include "voidptr.h"
 
-#include <string>
+#include <algorithm>
+#include <cstddef>
+#include <cstring>
 #include <iostream>
+#include <set>
 #include <sstream>
+#include <string>
 
 #if defined(__APPLE__)
 #include <dlfcn.h>
@@ -359,7 +357,7 @@ static void SbkDeallocWrapperCommon(PyObject *pyObj, bool canDelete)
     // Need to decref the type if this is the dealloc func; if type
     // is subclassed, that dealloc func will decref (see subtype_dealloc
     // in typeobject.c in the python sources)
-    auto dealloc = PyType_GetSlot(pyType, Py_tp_dealloc);
+    auto *dealloc = PyType_GetSlot(pyType, Py_tp_dealloc);
 
     // PYSIDE-939: Additional rule: Also when a subtype is heap allocated,
     // then the subtype_dealloc deref will be suppressed, and we need again
@@ -483,7 +481,7 @@ void SbkDeallocWrapperWithPrivateDtor(PyObject *self)
 void SbkObjectType_tp_dealloc(PyTypeObject *sbkType)
 {
     SbkObjectTypePrivate *sotp = PepType_SOTP(sbkType);
-    auto pyObj = reinterpret_cast<PyObject *>(sbkType);
+    auto *pyObj = reinterpret_cast<PyObject *>(sbkType);
 
     PyObject_GC_UnTrack(pyObj);
 #if !defined(Py_LIMITED_API) && !defined(PYPY_VERSION)
@@ -656,7 +654,7 @@ static PyObject *_setupNew(PyObject *obSelf, PyTypeObject *subtype)
     auto *self = reinterpret_cast<SbkObject *>(obSelf);
 
     Py_INCREF(obSubtype);
-    auto d = new SbkObjectPrivate;
+    auto *d = new SbkObjectPrivate;
 
     auto *sotp = PepType_SOTP(sbkSubtype);
     int numBases = ((sotp && sotp->is_multicpp) ?
@@ -736,6 +734,55 @@ bool SbkObjectType_Check(PyTypeObject *type)
     return Py_TYPE(type) == meta || PyType_IsSubtype(Py_TYPE(type), meta);
 }
 
+// Global functions from folding.
+
+// The common end.
+PyObject *Sbk_ReturnFromPython_None()
+{
+    if (Shiboken::Errors::occurred() != nullptr) {
+        return {};
+    }
+    Py_RETURN_NONE;
+}
+
+PyObject *Sbk_ReturnFromPython_Result(PyObject *pyResult)
+{
+    if (Shiboken::Errors::occurred() != nullptr || pyResult == nullptr) {
+        Py_XDECREF(pyResult);
+        return {};
+    }
+    return pyResult;
+}
+
+PyObject *Sbk_ReturnFromPython_Self(PyObject *self)
+{
+    if (Shiboken::Errors::occurred() != nullptr) {
+        return {};
+    }
+    Py_INCREF(self);
+    return self;
+}
+
+// The virtual function call
+PyObject *Sbk_GetPyOverride(const void *voidThis, Shiboken::GilState &gil, const char *funcName,
+                            bool *resultCache, PyObject **nameCache)
+{
+    PyObject *pyOverride{};
+    if (!*resultCache) {
+        gil.acquire();
+        pyOverride = Shiboken::BindingManager::instance().getOverride(voidThis, nameCache, funcName);
+        if (pyOverride == nullptr) {
+            *resultCache = true;
+            gil.release();
+        } else if (Shiboken::Errors::occurred() != nullptr) {
+            // Give up.
+            Py_XDECREF(pyOverride);
+            pyOverride = nullptr;
+        }
+    }
+    return pyOverride;
+}
+
 } //extern "C"
 
 
@@ -1018,6 +1065,16 @@ introduceWrapperType(PyObject *enclosingObject,
     setDestructorFunction(type, cppObjDtor);
     auto *ob_type = reinterpret_cast<PyObject *>(type);
 
+    if (wrapperFlags & InternalWrapper) {
+        // Type wraps another wrapper class and isn't part of any module.  This is used to extend
+        // Qt types with additional functionality, but from within PySide itself.  In order for
+        // the re-wrapped class to call the autogenerated __init__ method, is_user_type must be set
+        // to 1.  Otherwise the Shiboken::ObjectType::canCallConstructor test will fail.
+        // This is currently only used by QtRemoteObjects to create types dynamically.
+        sotp->is_user_type = 1;
+        return type;
+    }
+
     if (wrapperFlags & InnerClass) {
         // PYSIDE-2230: Instead of tp_dict, use the enclosing type.
         //              This stays interface compatible.
@@ -1103,7 +1160,7 @@ bool canDowncastTo(PyTypeObject *baseType, PyTypeObject *targetType)
 namespace Object
 {
 
-static void recursive_invalidate(SbkObject *self, std::set<SbkObject *>seen);
+static void recursive_invalidate(SbkObject *self, std::set<SbkObject *> &seen);
 
 bool checkType(PyObject *pyObj)
 {
@@ -1260,8 +1317,7 @@ void releaseOwnership(PyObject *self)
 }
 
 /* Needed forward declarations */
-static void recursive_invalidate(PyObject *pyobj, std::set<SbkObject *>& seen);
-static void recursive_invalidate(SbkObject *self, std::set<SbkObject *> &seen);
+static void recursive_invalidate(PyObject *pyobj, std::set<SbkObject *> &seen);
 
 void invalidate(PyObject *pyobj)
 {
@@ -1389,7 +1445,7 @@ bool isValid(PyObject *pyObj)
         return true;
     }
 
-    auto priv = reinterpret_cast<SbkObject *>(pyObj)->d;
+    auto *priv = reinterpret_cast<SbkObject *>(pyObj)->d;
 
     if (!priv->cppObjectCreated && isUserType(pyObj)) {
         PyErr_Format(PyExc_RuntimeError, "'__init__' method of object's base class (%s) not called.",
@@ -1671,8 +1727,8 @@ void setParent(PyObject *parent, PyObject *child)
     }
 
     bool parentIsNull = !parent || parent == Py_None;
-    auto parent_ = reinterpret_cast<SbkObject *>(parent);
-    auto child_ = reinterpret_cast<SbkObject *>(child);
+    auto *parent_ = reinterpret_cast<SbkObject *>(parent);
+    auto *child_ = reinterpret_cast<SbkObject *>(child);
 
     if (!parentIsNull) {
         if (!parent_->d->parentInfo)
index 08a2ff99383c19b509da1a65ad378c6e65de19c0..425328ae5e1ef279acd4cdd050d56fe05b796f13 100644 (file)
@@ -7,6 +7,7 @@
 #include "sbkpython.h"
 #include "shibokenmacros.h"
 #include "sbkmodule.h"
+#include "gilstate.h"
 
 #include <vector>
 #include <string>
@@ -115,6 +116,13 @@ LIBSHIBOKEN_API void disassembleFrame(const char *marker);
 /// PYSIDE-2230: Check if an object is an SbkObject.
 LIBSHIBOKEN_API bool SbkObjectType_Check(PyTypeObject *type);
 
+/// PYSIDE-2701: Some improvements from folding optimizations.
+LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_None();
+LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_Result(PyObject *pyResult);
+LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_Self(PyObject *self);
+LIBSHIBOKEN_API PyObject *Sbk_GetPyOverride(const void *voidThis, Shiboken::GilState &gil,
+                                            const char *funcName, bool *resultCache,
+                                            PyObject **nameCache);
 } // extern "C"
 
 namespace Shiboken
@@ -210,7 +218,8 @@ enum WrapperFlags
 {
     InnerClass = 0x1,
     DeleteInMainThread = 0x2,
-    Value = 0x4
+    Value = 0x4,
+    InternalWrapper = 0x8
 };
 
 /**
index 89990544eb9cf01c8d65b2ec5af8a9848d912e63..a5f7dff683536da5944a841d133a6b24a8d51fb1 100644 (file)
@@ -1,17 +1,16 @@
 // Copyright (C) 2016 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
+#include "bindingmanager.h"
+
 #include "autodecref.h"
 #include "basewrapper.h"
 #include "basewrapper_p.h"
-#include "bindingmanager.h"
-#include "gilstate.h"
 #include "helper.h"
+#include "sbkfeature_base.h"
 #include "sbkmodule.h"
-#include "sbkstring.h"
 #include "sbkstaticstrings.h"
-#include "sbkfeature_base.h"
-#include "debugfreehook.h"
+#include "sbkstring.h"
 
 #include <cstddef>
 #include <cstring>
@@ -290,7 +289,7 @@ void BindingManager::releaseWrapper(SbkObject *sbkObj)
     auto *d = PepType_SOTP(sbkType);
     int numBases = ((d && d->is_multicpp) ? getNumberOfCppBaseClasses(Py_TYPE(sbkObj)) : 1);
 
-    void ** cptrs = reinterpret_cast<SbkObject *>(sbkObj)->d->cptr;
+    void **cptrs = sbkObj->d->cptr;
     const int *mi_offsets = d != nullptr ? d->mi_offsets : nullptr;
     for (int i = 0; i < numBases; ++i) {
         if (cptrs[i] != nullptr)
@@ -368,9 +367,7 @@ PyObject *BindingManager::getOverride(const void *cptr,
                 Py_DECREF(method);
                 method = nullptr;
             }
-        } else if (PyObject_HasAttr(method, PyName::im_self())
-                   && PyObject_HasAttr(method, PyName::im_func())
-                   && PyObject_HasAttr(method, Shiboken::PyMagicName::code())) {
+        } else if (isCompiledMethod(method)) {
             PyObject *im_self = PyObject_GetAttr(method, PyName::im_self());
             // Not retaining a reference inline with what PyMethod_GET_SELF does.
             Py_DECREF(im_self);
@@ -392,11 +389,10 @@ PyObject *BindingManager::getOverride(const void *cptr,
     if (method != nullptr) {
         PyObject *mro = Py_TYPE(wrapper)->tp_mro;
 
-        int size = PyTuple_Size(mro);
         bool defaultFound = false;
         // The first class in the mro (index 0) is the class being checked and it should not be tested.
         // The last class in the mro (size - 1) is the base Python object class which should not be tested also.
-        for (int idx = 1; idx < size - 1; ++idx) {
+        for (Py_ssize_t idx = 1, size = PyTuple_Size(mro); idx < size - 1; ++idx) {
             auto *parent = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(mro, idx));
             AutoDecRef parentDict(PepType_GetDict(parent));
             if (parentDict) {
@@ -468,9 +464,9 @@ void BindingManager::dumpWrapperMap()
     std::cerr <<  "-------------------------------\n"
         << "WrapperMap size: " << wrapperMap.size() << " Types: "
         << m_d->classHierarchy.nodeSet().size() << '\n';
-    for (auto it = wrapperMap.begin(), end = wrapperMap.end(); it != end; ++it) {
-        const SbkObject *sbkObj = it->second;
-        std::cerr << "key: " << it->first << ", value: "
+    for (auto it : wrapperMap) {
+        const SbkObject *sbkObj = it.second;
+        std::cerr << "key: " << it.first << ", value: "
             << static_cast<const void *>(sbkObj) << " ("
             << (Py_TYPE(sbkObj))->tp_name << ", refcnt: "
             << Py_REFCNT(reinterpret_cast<const PyObject *>(sbkObj)) << ")\n";
index 8f7be6437300325204d5a0741a25f201278ac2b5..869f42f16756c36dad47afd5d0811b60021a80ba 100644 (file)
@@ -17,11 +17,9 @@ It will create an executable archive of the files on the command line.
 """
 
 import sys
-import os
 import argparse
-import pickle
 from textwrap import dedent
-from pathlib import path
+from pathlib import Path
 
 
 def source_archive(module, modname):
@@ -32,7 +30,6 @@ def source_archive(module, modname):
     # modname = module.__name__
     # Do not use: Some modules rename themselves!
     version = ".".join(map(str, sys.version_info[:3]))
-    shortname = fname.stem
     preamble = dedent(fr"""
         # BEGIN SOURCE ARCHIVE    Python {version}  module {modname}
 
@@ -66,7 +63,7 @@ if __name__ == "__main__":
     print("modules:", args.modules)
     ret = license_header() + read_all(args.modules)
     ma_mi = "_".join(map(str, sys.version_info[:2]))
-    outpath = Path(__file__).parents[2] / Path("shibokenmodule",
-        "files.dir", "shibokensupport", f"python_minilib_{ma_mi}.py")
+    outpath = Path(__file__).parents[2] / Path("shibokenmodule", "files.dir",
+                                               "shibokensupport", f"python_minilib_{ma_mi}.py")
     with outpath.open("w") as f:
         f.write(ret)
index 7458500b3a1459bbbd7777ebb25992fdc4e3eb2c..c36a2ca0ea9ba86f8d3f3b9a46e5eea61339ab40 100644 (file)
@@ -22,10 +22,7 @@ It is embedded into 'signature.cpp' as "embed/signature_bootstrap.inc".
 imports were in the functions. Moved them outside into the globals.
 """
 
-recursion_trap = 0
-
 import base64
-import importlib
 import io
 import os
 import sys
@@ -37,6 +34,9 @@ from importlib.machinery import ModuleSpec
 from pathlib import Path
 
 
+recursion_trap = 0
+
+
 def bootstrap():
 
     global recursion_trap
@@ -60,7 +60,7 @@ def bootstrap():
         for key in list(key for key in sys.modules if key.startswith(prefix)):
             del sys.modules[key]
         try:
-            import shibokensupport
+            import shibokensupport  # noqa: F401
             yield
         except Exception as e:
             f = sys.stderr
@@ -84,12 +84,11 @@ def bootstrap():
         from shibokensupport.signature import loader
     return loader
 
-# Newer functionality:
-# This function checks if the support directory exist and returns it.
-# If does not exist, we try to create it and return it.
-# Otherwise, we return None.
 
 def find_incarnated_files():
+    """Newer functionality: This function checks if the support directory exist and
+       returns it. If does not exist, we try to create it and return it. Otherwise,
+       we return None."""
     import shiboken6 as root
     files_dir = Path(root.__file__).resolve().parent / "files.dir"
     handle_embedding_switch(files_dir)
@@ -99,7 +98,7 @@ def find_incarnated_files():
         # But that has the side-effect that we need to delay the feature
         # initialization until all function pointers are set.
         # See `post_init_func` in signature_globals.cpp .
-        import shibokensupport.signature.loader
+        import shibokensupport.signature.loader  # noqa: F401
         del sys.path[0]
         return files_dir
     return None
@@ -129,7 +128,7 @@ def reincarnate_files(files_dir):
     try:
         # First check mkdir to get an error when we cannot write.
         files_dir.mkdir(exist_ok=True)
-    except os.error as e:
+    except os.error:
         print(f"SBK_EMBED=False: Warning: Cannot write into {files_dir}")
         return None
     try:
@@ -147,6 +146,7 @@ def reincarnate_files(files_dir):
 # a temporary zip file.
 # PYSIDE-1621: make zip file access totally virtual
 
+
 def prepare_zipfile():
     """
     Old approach:
@@ -162,7 +162,7 @@ def prepare_zipfile():
     """
 
     # 'zipstring_sequence' comes from signature.cpp
-    zipbytes = base64.b64decode(''.join(zipstring_sequence))
+    zipbytes = base64.b64decode(''.join(zipstring_sequence))   # noqa: F821
     vzip = zipfile.ZipFile(io.BytesIO(zipbytes))
     return sys.meta_path, EmbeddableZipImporter(vzip)
 
@@ -178,7 +178,7 @@ class EmbeddableZipImporter:
             return None
 
         self.zfile = zip_file
-        self._mod2path = {p2m(_.filename) : _.filename for _ in zip_file.filelist}
+        self._mod2path = {p2m(_.filename): _.filename for _ in zip_file.filelist}
 
     def find_spec(self, fullname, path, target=None):
         path = self._mod2path.get(fullname)
index 4414de648e428ae31b9a1203b4c99fce4eeed133..b5175bd3a0977308e8cd50b43768269d32d98108 100644 (file)
@@ -6,9 +6,9 @@
 namespace Shiboken
 {
 
-GilState::GilState()
+GilState::GilState(bool acquire)
 {
-    if (Py_IsInitialized()) {
+    if (acquire && Py_IsInitialized()) {
         m_gstate = PyGILState_Ensure();
         m_locked = true;
     }
@@ -19,6 +19,14 @@ GilState::~GilState()
     release();
 }
 
+void GilState::acquire()
+{
+    if (Py_IsInitialized()) {
+        m_gstate = PyGILState_Ensure();
+        m_locked = true;
+    }
+}
+
 void GilState::release()
 {
     if (m_locked && Py_IsInitialized()) {
index 1714d77a53519334f723bcf808f9426471a7e1c1..c23ad4505c206ea303b237086acb02821f843672 100644 (file)
@@ -18,8 +18,9 @@ public:
     GilState &operator=(const GilState &) = delete;
     GilState &operator=(GilState &&) = delete;
 
-    GilState();
+    explicit GilState(bool acquire=true);
     ~GilState();
+    void acquire();
     void release();
     void abandon();
 private:
index dc81bbb29d89493975ca7781b33b4e1df91dfb72..92f70f1549f2f90a5511c2f160ada22742583b4e 100644 (file)
@@ -5,12 +5,10 @@
 #include "basewrapper_p.h"
 #include "sbkstring.h"
 #include "sbkstaticstrings.h"
-#include "sbkstaticstrings.h"
 #include "pep384impl.h"
 
 #include <algorithm>
 #include <optional>
-
 #include <iomanip>
 #include <iostream>
 #include <climits>
@@ -176,8 +174,8 @@ static void formatPyTuple(PyObject *obj, std::ostream &str)
 
 static void formatPyDict(PyObject *obj, std::ostream &str)
 {
-    PyObject *key;
-    PyObject *value;
+    PyObject *key{};
+    PyObject *value{};
     Py_ssize_t pos = 0;
     str << '{';
     while (PyDict_Next(obj, &pos, &key, &value) != 0) {
@@ -470,54 +468,66 @@ static char *toWindowsConsoleEncoding(PyObject *unicode)
 }
 #endif // _WIN32
 
+static char *strDup(const char *s) // strdup() using new[] for symmetry with the Windows code
+{
+    auto len = std::strlen(s);
+    auto *result = new char[1 + len];
+    // copy len-characters to avoid if we have a null terminator in the middle.
+    std::memcpy(result, s, len);
+    result[len] = '\0';
+    return result;
+}
+
 // PySide-510: Changed from PySequence to PyList, which is correct.
-bool listToArgcArgv(PyObject *argList, int *argc, char ***argv, const char *defaultAppName)
+bool listToArgcArgv(PyObject *argList, int *argcIn, char ***argvIn, const char *defaultAppName)
 {
+    *argcIn = 0;
+    *argvIn = nullptr;
     if (!PyList_Check(argList))
         return false;
 
-    if (!defaultAppName)
-        defaultAppName = "PySideApplication";
-
-    // Check all items
     Shiboken::AutoDecRef args(PySequence_Fast(argList, nullptr));
-    Py_ssize_t numArgs = PySequence_Size(argList);
-    for (Py_ssize_t i = 0; i < numArgs; ++i) {
-        PyObject *item = PyList_GET_ITEM(args.object(), i);
-        if (!PyBytes_Check(item) && !PyUnicode_Check(item))
-            return false;
+    const Py_ssize_t numArgs = PySequence_Size(argList);
+    if (numArgs == 0) { // Try to get the script name
+        auto *argv = new char *[1];
+        *argvIn = argv;
+        *argcIn = 1;
+        if (PyObject *appName = PyDict_GetItem(PyEval_GetGlobals(), Shiboken::PyMagicName::file()))
+            argv[0] = strDup(Shiboken::String::toCString(appName));
+        else
+            argv[0] = strDup(defaultAppName ? defaultAppName : "PySideApplication");
+        return true;
     }
 
-    bool hasEmptyArgList = numArgs == 0;
-    if (hasEmptyArgList)
-        numArgs = 1;
-
-    *argc = numArgs;
-    *argv = new char *[*argc];
+    auto *argv = new char *[numArgs];
+    std::fill(argv, argv + numArgs, nullptr);
 
-    if (hasEmptyArgList) {
-        // Try to get the script name
-        PyObject *globals = PyEval_GetGlobals();
-        PyObject *appName = PyDict_GetItem(globals, Shiboken::PyMagicName::file());
-        (*argv)[0] = strdup(appName ? Shiboken::String::toCString(appName) : defaultAppName);
-    } else {
-        for (int i = 0; i < numArgs; ++i) {
-            PyObject *item = PyList_GET_ITEM(args.object(), i);
-            char *string = nullptr;
-            if (Shiboken::String::check(item)) {
+    for (Py_ssize_t i = 0; i < numArgs; ++i) {
+        PyObject *item = PyList_GetItem(args.object(), i);
+        if (Shiboken::String::check(item)) {
 #ifdef _WIN32
-                string = toWindowsConsoleEncoding(item);
+            argv[i] = toWindowsConsoleEncoding(item);
 #else
-                string = strdup(Shiboken::String::toCString(item));
+            argv[i] = strDup(Shiboken::String::toCString(item));
 #endif
-            }
-            (*argv)[i] = string;
+        } else {
+            deleteArgv(int(i), argv);
+            return false;
         }
     }
 
+    *argcIn = int(numArgs);
+    *argvIn = argv;
     return true;
 }
 
+void deleteArgv(int argc, char **argv)
+{
+    for (int a = 0; a < argc; ++a)
+        delete [] argv[a];
+    delete [] argv;
+}
+
 int *sequenceToIntArray(PyObject *obj, bool zeroTerminated)
 {
     AutoDecRef seq(PySequence_Fast(obj, "Sequence of ints expected"));
@@ -527,7 +537,7 @@ int *sequenceToIntArray(PyObject *obj, bool zeroTerminated)
     Py_ssize_t size = PySequence_Size(seq.object());
     int *array = new int[size + (zeroTerminated ? 1 : 0)];
 
-    for (int i = 0; i < size; i++) {
+    for (Py_ssize_t i = 0; i < size; i++) {
         Shiboken::AutoDecRef item(PySequence_GetItem(seq.object(), i));
         if (!PyLong_Check(item)) {
             PyErr_SetString(PyExc_TypeError, "Sequence of ints expected");
@@ -557,7 +567,7 @@ int warning(PyObject *category, int stacklevel, const char *format, ...)
 
     // check the necessary memory
     int size = vsnprintf(nullptr, 0, format, args) + 1;
-    auto message = new char[size];
+    auto *message = new char[size];
     int result = 0;
     if (message) {
         // format the message
@@ -612,6 +622,14 @@ const char *typeNameOf(const char *typeIdName)
     return result;
 }
 
+bool isCompiledMethod(PyObject *method)
+{
+    return method != nullptr && method != Py_None
+        && PyObject_HasAttr(method, PyName::im_self()) != 0
+        && PyObject_HasAttr(method, PyName::im_func()) != 0
+        && PyObject_HasAttr(method, Shiboken::PyMagicName::code()) != 0;
+}
+
 #if !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030A0000 && !defined(PYPY_VERSION)
 static int _getPyVerbose()
 {
index f226e8c2499c371878fb2bfc037e4e8af72ae056..ccd80b2dbf9afe8bbe10aa54c9adace6b1b1ca60 100644 (file)
@@ -28,6 +28,9 @@ namespace Shiboken
 */
 LIBSHIBOKEN_API bool listToArgcArgv(PyObject *argList, int *argc, char ***argv, const char *defaultAppName = nullptr);
 
+/// Delete a a list of arguments created by listToArgcArgv()
+LIBSHIBOKEN_API void deleteArgv(int argc, char **argv);
+
 /**
  * Convert a python sequence into a heap-allocated array of ints.
  *
@@ -40,27 +43,32 @@ LIBSHIBOKEN_API int *sequenceToIntArray(PyObject *obj, bool zeroTerminated = fal
 /// \returns Fixed name (allocated).
 LIBSHIBOKEN_API const char *typeNameOf(const char *typeIdName);
 
+/// Returns whether \a method is a compiled method (Nuitka).
+LIBSHIBOKEN_API bool isCompiledMethod(PyObject *method);
+
 /**
  *  Creates and automatically deallocates C++ arrays.
  */
 template<class T>
-class AutoArrayPointer
+class ArrayPointer
 {
     public:
-        AutoArrayPointer(const AutoArrayPointer &) = delete;
-        AutoArrayPointer(AutoArrayPointer &&) = delete;
-        AutoArrayPointer &operator=(const AutoArrayPointer &) = delete;
-        AutoArrayPointer &operator=(AutoArrayPointer &&) = delete;
+        ArrayPointer(const ArrayPointer &) = delete;
+        ArrayPointer(ArrayPointer &&) = delete;
+        ArrayPointer &operator=(const ArrayPointer &) = delete;
+        ArrayPointer &operator=(ArrayPointer &&) = delete;
 
-
-        explicit AutoArrayPointer(Py_ssize_t size) { data = new T[size]; }
+        explicit ArrayPointer(Py_ssize_t size) : data(new T[size]) {}
         T &operator[](Py_ssize_t pos) { return data[pos]; }
         operator T *() const { return data; }
-        ~AutoArrayPointer() { delete[] data; }
+        ~ArrayPointer() { delete[] data; }
     private:
         T *data;
 };
 
+template <class T>
+using AutoArrayPointer = ArrayPointer<T>; // deprecated
+
 using ThreadId = unsigned long long;
 LIBSHIBOKEN_API ThreadId currentThreadId();
 LIBSHIBOKEN_API ThreadId mainThreadId();
index dc067cd5247996a8db7c5470bcfa580d401fe8ec..bd7a4c51aa9c781a6acaa2a8dd454d91feec8b5e 100644 (file)
@@ -811,31 +811,6 @@ Pep_GetPartialFunction(void)
  * Newly introduced convenience functions
  *
  */
-#ifdef Py_LIMITED_API
-
-PyObject *
-PyImport_GetModule(PyObject *name)
-{
-    PyObject *modules = PyImport_GetModuleDict();
-    if (modules == nullptr) {
-        PyErr_SetString(PyExc_RuntimeError, "unable to get sys.modules");
-        return nullptr;
-    }
-    Py_INCREF(modules);
-    PyObject *m{};
-    if (PyDict_CheckExact(modules)) {
-        m = PyDict_GetItemWithError(modules, name);  /* borrowed */
-        Py_XINCREF(m);
-    } else {
-        m = PyObject_GetItem(modules, name);
-        if (m == nullptr && PyErr_ExceptionMatches(PyExc_KeyError))
-            PyErr_Clear();
-    }
-    Py_DECREF(modules);
-    return m;
-}
-
-#endif // Py_LIMITED_API
 
 // 2020-06-16: For simplicity of creating arbitrary things, this function
 // is now made public.
index d3d644649939ef12fc3ca6dd3457177a39ecc988..4c4e1b47ea18a709d361afea6d9150432837511b 100644 (file)
@@ -265,47 +265,6 @@ enum PepUnicode_Kind {
 #define _PepUnicode_IS_ASCII     PyUnicode_IS_ASCII
 #endif
 
-/*****************************************************************************
- *
- * RESOLVED: bytesobject.h
- *
- */
-#ifdef Py_LIMITED_API
-#define PyBytes_AS_STRING(op)       PyBytes_AsString(op)
-#define PyBytes_GET_SIZE(op)        PyBytes_Size(op)
-#endif
-
-/*****************************************************************************
- *
- * RESOLVED: floatobject.h
- *
- */
-#ifdef Py_LIMITED_API
-#define PyFloat_AS_DOUBLE(op)       PyFloat_AsDouble(op)
-#endif
-
-/*****************************************************************************
- *
- * RESOLVED: tupleobject.h
- *
- */
-#ifdef Py_LIMITED_API
-#define PyTuple_GET_ITEM(op, i)     PyTuple_GetItem((PyObject *)op, i)
-#define PyTuple_SET_ITEM(op, i, v)  PyTuple_SetItem(op, i, v)
-#define PyTuple_GET_SIZE(op)        PyTuple_Size((PyObject *)op)
-#endif
-
-/*****************************************************************************
- *
- * RESOLVED: listobject.h
- *
- */
-#ifdef Py_LIMITED_API
-#define PyList_GET_ITEM(op, i)      PyList_GetItem(op, i)
-#define PyList_SET_ITEM(op, i, v)   PyList_SetItem(op, i, v)
-#define PyList_GET_SIZE(op)         PyList_Size(op)
-#endif
-
 /*****************************************************************************
  *
  * RESOLVED: methodobject.h
@@ -315,13 +274,11 @@ enum PepUnicode_Kind {
 #ifdef Py_LIMITED_API
 
 using PyCFunctionObject = struct _pycfunc;
-#define PyCFunction_GET_FUNCTION(func)  PyCFunction_GetFunction((PyObject *)func)
-#define PyCFunction_GET_SELF(func)      PyCFunction_GetSelf((PyObject *)func)
-#define PyCFunction_GET_FLAGS(func)     PyCFunction_GetFlags((PyObject *)func)
 #define PepCFunction_GET_NAMESTR(func) \
     _PepUnicode_AsString(PyObject_GetAttrString((PyObject *)func, "__name__"))
 #else
-#define PepCFunction_GET_NAMESTR(func)        ((func)->m_ml->ml_name)
+#define PepCFunction_GET_NAMESTR(func) \
+    (reinterpret_cast<const PyCFunctionObject *>(func)->m_ml->ml_name)
 #endif
 
 /*****************************************************************************
@@ -558,9 +515,6 @@ extern LIBSHIBOKEN_API PyTypeObject *PepBuiltinMethod_TypePtr;
  *
  * This is not defined if Py_LIMITED_API is defined.
  */
-#ifdef Py_LIMITED_API
-LIBSHIBOKEN_API PyObject *PyImport_GetModule(PyObject *name);
-#endif // Py_LIMITED_API
 
 // Evaluate a script and return the variable `result`
 LIBSHIBOKEN_API PyObject *PepRun_GetResult(const char *command);
index 886baf1bbd5b7571c295d7104a6bd8850f1635c4..ad8120d08b313568f63fc08eed7b49a2fb32310d 100644 (file)
@@ -5,7 +5,6 @@
 #include "sbkarrayconverter_p.h"
 #include "helper.h"
 #include "sbkconverter.h"
-#include "sbkconverter_p.h"
 
 #include <longobject.h>
 #include <floatobject.h>
diff --git a/sources/shiboken6/libshiboken/sbkbindingutils.cpp b/sources/shiboken6/libshiboken/sbkbindingutils.cpp
new file mode 100644 (file)
index 0000000..e01c4fc
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "sbkbindingutils.h"
+
+#include "autodecref.h"
+
+#include <algorithm>
+
+namespace Shiboken {
+
+static const ArgumentNameIndexMapping *
+    findMapping(const ArgumentNameIndexMapping *i1,
+                const ArgumentNameIndexMapping *i2, PyObject *needle)
+{
+    return std::find_if(i1, i2, [needle](const ArgumentNameIndexMapping &m) {
+        return PyUnicode_CompareWithASCIIString(needle, m.name) == 0;
+    });
+}
+
+bool parseKeywordArguments(PyObject *kwds,
+                           const ArgumentNameIndexMapping *mapping, size_t size,
+                           Shiboken::AutoDecRef &errInfo, PyObject **pyArgs)
+{
+    if (kwds == nullptr || PyDict_Size(kwds) == 0)
+        return true;
+    PyObject *key{};
+    PyObject *value{};
+    Py_ssize_t pos = 0;
+    const ArgumentNameIndexMapping *mappingEnd = mapping + size;
+    while (PyDict_Next(kwds, &pos, &key, &value) != 0) {
+        auto it = findMapping(mapping, mappingEnd, key);
+        // Missing key: Create a new dict as error context (see signature/errorhandler.py)
+        if (it == mappingEnd) {
+            errInfo.reset(PyDict_New());
+            PyDict_SetItem(errInfo.object(), key, value);
+            return false;
+        }
+        if (pyArgs[it->index] != nullptr) { // duplicate entry, set string as error context
+            errInfo.reset(key);
+            return false;
+        }
+        pyArgs[it->index] = value;
+    }
+    return true;
+}
+
+bool parseConstructorKeywordArguments(PyObject *kwds,
+                                      const ArgumentNameIndexMapping *mapping, size_t size,
+                                      Shiboken::AutoDecRef &errInfo, PyObject **pyArgs)
+{
+    assert(kwds);
+    Shiboken::AutoDecRef result(PyDict_New());
+    PyObject *key{};
+    PyObject *value{};
+    Py_ssize_t pos = 0;
+    const ArgumentNameIndexMapping *mappingEnd = mapping + size;
+    while (PyDict_Next(kwds, &pos, &key, &value) != 0) {
+        auto it = findMapping(mapping, mappingEnd, key);
+        // Ignore missing key, assuming it is a property to be handled later
+        if (it != mappingEnd) {
+            // duplicate entry, set string as error context (see signature/errorhandler.py)
+            if (pyArgs[it->index] != nullptr) {
+                errInfo.reset(key);
+                return false;
+            }
+            pyArgs[it->index] = value;
+        } else {
+            PyDict_SetItem(result.object(), key, value);
+        }
+    }
+    errInfo.reset(result.release());
+    return true;
+}
+
+} // namespace Shiboken
diff --git a/sources/shiboken6/libshiboken/sbkbindingutils.h b/sources/shiboken6/libshiboken/sbkbindingutils.h
new file mode 100644 (file)
index 0000000..4ed833d
--- /dev/null
@@ -0,0 +1,40 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef SBK_BINDINGUTILS
+#define SBK_BINDINGUTILS
+
+#include "sbkpython.h"
+#include "shibokenmacros.h"
+
+namespace Shiboken {
+struct AutoDecRef;
+
+/// Maps a keyword argument by name to its parameter index
+struct ArgumentNameIndexMapping
+{
+    const char *name;
+    int index;
+};
+
+/// Function binding helper: Parse the keyword arguments in dict \a kwds
+/// according to \a mapping (name->index) and store them in array \a pyArgs
+/// under their index. Fails if an entry is missing or duplicate entries
+/// occur.
+LIBSHIBOKEN_API bool
+    parseKeywordArguments(PyObject *kwds,
+                          const ArgumentNameIndexMapping *mapping, size_t size,
+                          Shiboken::AutoDecRef &errInfo, PyObject **pyArgs);
+
+/// Function binding helper: Parse the keyword arguments of a QObject constructor
+/// in dict \a kwds according to \a mapping (name->index) and store them in array
+/// \a pyArgs under their index. Fails if duplicate entries occur. Unmapped entries
+/// (QObject properties) are stored in a dict in errInfo for further processing.
+LIBSHIBOKEN_API bool
+    parseConstructorKeywordArguments(PyObject *kwds,
+                                     const ArgumentNameIndexMapping *mapping, size_t size,
+                                     Shiboken::AutoDecRef &errInfo, PyObject **pyArgs);
+
+} // namespace Shiboken
+
+#endif // SBK_BINDINGUTILS
index 97d7a3628a83eb2ab862413cbe34917e680356dc..b8634002cf76808ffc4b78c7b34979280e237409 100644 (file)
@@ -742,8 +742,8 @@ bool checkDictTypes(PyTypeObject *keyType, PyTypeObject *valueType, PyObject *py
     if (!PyDict_Check(pyIn))
         return false;
 
-    PyObject *key;
-    PyObject *value;
+    PyObject *key{};
+    PyObject *value{};
     Py_ssize_t pos = 0;
     while (PyDict_Next(pyIn, &pos, &key, &value)) {
         if (!PyObject_TypeCheck(key, keyType))
@@ -789,8 +789,8 @@ bool convertibleDictTypes(const SbkConverter *keyConverter, bool keyCheckExact,
     assert(pyIn);
     if (!PyDict_Check(pyIn))
         return false;
-    PyObject *key;
-    PyObject *value;
+    PyObject *key{};
+    PyObject *value{};
     Py_ssize_t pos = 0;
     while (PyDict_Next(pyIn, &pos, &key, &value)) {
         if (keyCheckExact) {
@@ -818,8 +818,8 @@ bool convertibleMultiDictTypes(const SbkConverter *keyConverter, bool keyCheckEx
     assert(pyIn);
     if (!PyDict_Check(pyIn))
         return false;
-    PyObject *key;
-    PyObject *values;
+    PyObject *key{};
+    PyObject *values{};
     Py_ssize_t pos = 0;
     while (PyDict_Next(pyIn, &pos, &key, &values)) {
         if (keyCheckExact) {
@@ -881,7 +881,7 @@ SpecificConverter::SpecificConverter(const char *typeName)
     m_converter = getConverter(typeName);
     if (!m_converter)
         return;
-    const Py_ssize_t len = strlen(typeName);
+    const auto len = strlen(typeName);
     char lastChar = typeName[len -1];
     if (lastChar == '&') {
         m_type = ReferenceConversion;
index 22d0f923f454ae22ee4b7fa00967be6b570a1d59..a050844f207e9ef6849a1d4591b541ee4f9647c2 100644 (file)
@@ -88,13 +88,13 @@ public:
 
     explicit SpecificConverter(const char *typeName);
 
-    inline SbkConverter *converter() { return m_converter; }
-    inline operator SbkConverter *() const { return m_converter; }
+    SbkConverter *converter() { return m_converter; }
+    operator SbkConverter *() const { return m_converter; }
 
-    inline bool isValid() { return m_type != InvalidConversion; }
-    inline operator bool() const { return m_type != InvalidConversion; }
+    bool isValid() { return m_type != InvalidConversion; }
+    operator bool() const { return m_type != InvalidConversion; }
 
-    inline Type conversionType() { return m_type; }
+    Type conversionType() { return m_type; }
 
     PyObject *toPython(const void *cppIn);
     void toCpp(PyObject *pyIn, void *cppOut);
index 787d4ac0db02e43de8599043e93c32b2c3fd1e45..212fcec861f4f0da1fa55a1a754f591687609f20 100644 (file)
@@ -12,8 +12,6 @@
 #include "sbktypefactory.h"
 
 #include <cstring>
-#include <vector>
-#include <sstream>
 
 using namespace Shiboken;
 
@@ -226,7 +224,7 @@ bool checkType(PyTypeObject *pyTypeObj)
     init_enum();
 
     static PyTypeObject *meta = getPyEnumMeta();
-    return Py_TYPE(pyTypeObj) == reinterpret_cast<PyTypeObject *>(meta);
+    return Py_TYPE(pyTypeObj) == meta;
 }
 
 PyObject *getEnumItemFromValue(PyTypeObject *enumType, EnumValueType itemValue)
@@ -282,9 +280,7 @@ void setTypeConverter(PyTypeObject *type, SbkConverter *converter,
     priv->flagsConverter = flagsConverter;
 }
 
-static PyTypeObject *createEnumForPython(PyObject *scopeOrModule,
-                                         const char *fullName,
-                                         PyObject *pyEnumItems)
+static void setModuleAndQualnameOnType(PyObject *type, const char *fullName)
 {
     const char *colon = strchr(fullName, ':');
     assert(colon);
@@ -301,13 +297,22 @@ static PyTypeObject *createEnumForPython(PyObject *scopeOrModule,
     int mlen = qual - mod - 1;
     AutoDecRef module(Shiboken::String::fromCString(mod, mlen));
     AutoDecRef qualname(Shiboken::String::fromCString(qual));
-    const char *dot = strrchr(qual, '.');
-    AutoDecRef name(Shiboken::String::fromCString(dot ? dot + 1 : qual));
+
+    PyObject_SetAttr(type, Shiboken::PyMagicName::module(), module);
+    PyObject_SetAttr(type, Shiboken::PyMagicName::qualname(), qualname);
+}
+
+static PyTypeObject *createEnumForPython(PyObject *scopeOrModule,
+                                         const char *fullName,
+                                         PyObject *pyEnumItems)
+{
+    const char *dot = strrchr(fullName, '.');
+    AutoDecRef name(Shiboken::String::fromCString(dot ? dot + 1 : fullName));
 
     static PyObject *enumName = String::createStaticString("IntEnum");
     if (PyType_Check(scopeOrModule)) {
         // For global objects, we have no good solution, yet where to put the int info.
-        auto type = reinterpret_cast<PyTypeObject *>(scopeOrModule);
+        auto *type = reinterpret_cast<PyTypeObject *>(scopeOrModule);
         auto *sotp = PepType_SOTP(type);
         if (!sotp->enumFlagsDict)
             initEnumFlagsDict(type);
@@ -352,9 +357,7 @@ static PyTypeObject *createEnumForPython(PyObject *scopeOrModule,
         }
     }
 
-    auto *newType = reinterpret_cast<PyTypeObject *>(obNewType);
-    PyObject_SetAttr(obNewType, PyMagicName::qualname(), qualname);
-    PyObject_SetAttr(obNewType, PyMagicName::module(), module);
+    setModuleAndQualnameOnType(obNewType, fullName);
 
     // See if we should re-introduce shortcuts in the enclosing object.
     const bool useGlobalShortcut = (Enum::enumOption & Enum::ENOPT_GLOBAL_SHORTCUT) != 0;
@@ -374,7 +377,7 @@ static PyTypeObject *createEnumForPython(PyObject *scopeOrModule,
         }
     }
 
-    return newType;
+    return reinterpret_cast<PyTypeObject *>(obNewType);
 }
 
 template <typename IntT>
@@ -459,4 +462,25 @@ PyTypeObject *createPythonEnum(PyObject *module,
     return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues);
 }
 
+PyTypeObject *createPythonEnum(const char *fullName, PyObject *pyEnumItems,
+                               const char *enumTypeName, PyObject *callDict)
+{
+    SBK_UNUSED(getPyEnumMeta());
+    AutoDecRef PyEnumTypeName(Shiboken::String::fromCString(enumTypeName));
+    AutoDecRef PyEnumType(PyObject_GetAttr(PyEnumModule, PyEnumTypeName));
+    if (!PyEnumType) {
+        PyErr_Format(PyExc_RuntimeError, "Failed to get enum type %s", enumTypeName);
+        return nullptr;
+    }
+
+    const char *dot = strrchr(fullName, '.');
+    AutoDecRef name(Shiboken::String::fromCString(dot ? dot + 1 : fullName));
+    AutoDecRef callArgs(Py_BuildValue("(OO)", name.object(), pyEnumItems));
+    auto *newType = PyObject_Call(PyEnumType, callArgs, callDict);
+
+    setModuleAndQualnameOnType(newType, fullName);
+
+    return reinterpret_cast<PyTypeObject *>(newType);
+}
+
 } // namespace Shiboken::Enum
index 55503b5ec89879ca31547d79540b4abd59f7767f..8dcf5b7009dac3e44e8b35260bd6c0ac5973bc2a 100644 (file)
@@ -100,6 +100,22 @@ inline PyTypeObject *createPythonEnum(PyTypeObject *scope,
     return createPythonEnum(obScope, fullName, enumItemStrings, enumValues);
 }
 
+/**
+ * @brief Creates a Python enum type from a set of provided key/values pairs
+ *
+ * @param fullName     The full name (including module and package depth) to be used for the newly
+ *                     created enum type.
+ * @param pyEnumItems  The key/value pairs to be used for the enum.
+ * @param enumTypeName The name of the enum type to be used (i.e., "PyIntEnum", "PyFlag", etc)
+ *                     from Python's enum module.
+ * @param callDict     The dictionary to be used for the call, allowing for additional keyword
+ *                     arguments to be passed, such as "boundary=KEEP".
+ */
+LIBSHIBOKEN_API PyTypeObject *createPythonEnum(const char *fullName,
+                                               PyObject *pyEnumItems,
+                                               const char *enumTypeName = "Enum",
+                                               PyObject *callDict = nullptr);
+
 } // namespace Shiboken::Enum
 
 #endif // SKB_PYENUM_H
index 39f67ddeb07af7285db75ea63d2ba56dbb505e4c..8dc3c639cb877e2a3b587e1760e3acd1d4b57501 100644 (file)
@@ -60,6 +60,16 @@ void setInstantiateAbstractClassDisabledWrapper(const char *name)
                  name);
 }
 
+void setInstantiateNamespace(const char *name)
+{
+    PyErr_Format(PyExc_NotImplementedError, "Namespace '%s' cannot be instantiated.", name);
+}
+
+void setInstantiateNonConstructible(const char *name)
+{
+    PyErr_Format(PyExc_NotImplementedError, "Class '%s' cannot be instantiated.", name);
+}
+
 void setInvalidTypeDeletion(const char *name)
 {
     PyErr_Format(PyExc_TypeError, "'%s' may not be deleted", name);
index 2926cbc654951ec1d89f30763e58c6a3b41c2192..d7247ded4a54096c46e23a822182377c5f40e9fc 100644 (file)
@@ -39,6 +39,8 @@ LIBSHIBOKEN_API void setIndexOutOfBounds(Py_ssize_t value, Py_ssize_t minValue,
                                          Py_ssize_t maxValue);
 LIBSHIBOKEN_API void setInstantiateAbstractClass(const char *name);
 LIBSHIBOKEN_API void setInstantiateAbstractClassDisabledWrapper(const char *name);
+LIBSHIBOKEN_API void setInstantiateNamespace(const char *name);
+LIBSHIBOKEN_API void setInstantiateNonConstructible(const char *name);
 LIBSHIBOKEN_API void setInvalidTypeDeletion(const char *name);
 LIBSHIBOKEN_API void setOperatorNotImplemented();
 LIBSHIBOKEN_API void setPureVirtualMethodError(const char *name);
index fe32d8ca2bece8fa7ac7c5debc9f84d184ad9d29..b778367a1684a1b26741352c1837d7e18dd8fb75 100644 (file)
@@ -92,13 +92,35 @@ void disassembleFrame(const char *marker)
     PyErr_Restore(error_type, error_value, error_traceback);
 }
 
-// Python 3.13
-static int const LOAD_ATTR_313 = 82;
-static int const CALL_313 = 53;
-static int const PUSH_NULL_313 = 34;
-// Python 3.12
-static int const CALL_312 = 171;
-// Python 3.11
+// OpCodes: Adapt for each Python version by checking the defines in the generated header opcode_ids.h
+// egrep '( LOAD_ATTR | CALL | PUSH_NULL )' opcode_ids.h
+// See also test sources/pyside6/tests/pysidetest/enum_test.py
+
+static int constexpr LOAD_ATTR_OpCode(long pyVersion)
+{
+    if (pyVersion >= 0x030E00) // 3.14
+        return 80;
+    if (pyVersion >= 0x030D00) // 3.13
+        return 82;
+    return 106;
+}
+
+static int constexpr CALL_OpCode(long pyVersion)
+{
+    if (pyVersion >= 0x030E00) // 3.14
+        return 52;
+    if (pyVersion >= 0x030D00) // 3.13
+        return 53;
+    return 171;
+}
+
+static int constexpr PUSH_NULL_OpCode(long pyVersion)
+{
+    if (pyVersion >= 0x030E00) // 3.14
+        return 33;
+    return 34;
+}
+
 static int const PRECALL = 166;
 // we have "big instructions" with gaps after them
 static int const LOAD_METHOD_GAP_311 = 10 * 2;
@@ -107,23 +129,11 @@ static int const LOAD_ATTR_GAP = 9 * 2;
 // Python 3.7 - 3.10
 static int const LOAD_METHOD = 160;
 static int const CALL_METHOD = 161;
-// Python 3.6
-static int const CALL_FUNCTION = 131;
-static int const LOAD_ATTR_312 = 106;
-// NoGil (how long will this exist in this form?)
-static int const LOAD_METHOD_NOGIL = 55;
-static int const CALL_METHOD_NOGIL = 72;
 
 static bool currentOpcode_Is_CallMethNoArgs()
 {
-    static auto number = _PepRuntimeVersion();
-    static int LOAD_ATTR = number < 0x030D00 ? LOAD_ATTR_312 : LOAD_ATTR_313;
-    static int CALL = number < 0x030D00 ? CALL_312 : CALL_313;
-    // PYSIDE-2221: Special case for the NoGil version:
-    //              Find out if we have such a version.
-    //              We could also ask the variable `Py_NOGIL`.
+    static const auto number = _PepRuntimeVersion();
     static PyObject *flags = PySys_GetObject("flags");
-    static bool isNoGil = PyObject_HasAttrString(flags, "nogil");
     // We look into the currently active operation if we are going to call
     // a method with zero arguments.
     auto *frame = PyEval_GetFrame();
@@ -150,23 +160,18 @@ static bool currentOpcode_Is_CallMethNoArgs()
     char *co_code{};
     PyBytes_AsStringAndSize(dec_co_code, &co_code, &code_len);
     uint8_t opcode1 = co_code[f_lasti];
-    if (isNoGil) {
-        uint8_t opcode2 = co_code[f_lasti + 4];
-        uint8_t oparg2 = co_code[f_lasti + 6];
-        return opcode1 == LOAD_METHOD_NOGIL && opcode2 == CALL_METHOD_NOGIL && oparg2 == 1;
-    }
     uint8_t opcode2 = co_code[f_lasti + 2];
     uint8_t oparg2 = co_code[f_lasti + 3];
-    if (number < 0x030B00)
+    if (number < 0x030B00) // pre 3.11
         return opcode1 == LOAD_METHOD && opcode2 == CALL_METHOD && oparg2 == 0;
 
-    if (number < 0x030C00) {
+    if (number < 0x030C00) { // pre 3.12
         // With Python 3.11, the opcodes get bigger and change a bit.
         // Note: The new adaptive opcodes are elegantly hidden and we
         //       don't need to take care of them.
         if (opcode1 == LOAD_METHOD)
             f_lasti += LOAD_METHOD_GAP_311;
-        else if (opcode1 == LOAD_ATTR_312)
+        else if (opcode1 == LOAD_ATTR_OpCode(0x030C00)) // 3.12
             f_lasti += LOAD_ATTR_GAP_311;
         else
             return false;
@@ -179,20 +184,20 @@ static bool currentOpcode_Is_CallMethNoArgs()
     // With Python 3.12, the opcodes get again bigger and change a bit.
     // Note: The new adaptive opcodes are elegantly hidden and we
     //       don't need to take care of them.
-    if (opcode1 == LOAD_ATTR)
+    if (opcode1 == LOAD_ATTR_OpCode(number))
         f_lasti += LOAD_ATTR_GAP;
     else
         return false;
 
-    if (number >= 0x030D00) {
+    if (number >= 0x030D00) {  // starting with 3.13
         int opcode3 = co_code[f_lasti + 2];
-        if (opcode3 == PUSH_NULL_313)
+        if (opcode3 == PUSH_NULL_OpCode(number))
             f_lasti += 2;
     }
     opcode2 = co_code[f_lasti + 2];
     oparg2 = co_code[f_lasti + 3];
 
-    return opcode2 == CALL && oparg2 == 0;
+    return opcode2 == CALL_OpCode(number) && oparg2 == 0;
 }
 
 void initEnumFlagsDict(PyTypeObject *type)
index f254cdfd14e4806c4b431dabb8500f2e4768c597..865978a14dd97a193efcebd07844e26580d729b9 100644 (file)
@@ -102,7 +102,8 @@ static void incarnateHelper(PyObject *module, const std::string_view names,
     auto initFunc = tcStruct.func;
     PyTypeObject *type = initFunc(modOrType);
     auto name = names.substr(startPos);
-    PyObject_SetAttrString(modOrType, name.data(), reinterpret_cast<PyObject *>(type));
+    AutoDecRef nameP(PyUnicode_FromStringAndSize(name.data(), name.size()));
+    PyObject_SetAttr(modOrType, nameP, reinterpret_cast<PyObject *>(type));
 }
 
 static void incarnateSubtypes(PyObject *module,
@@ -263,6 +264,27 @@ static int const IMPORT_NAME_312 = 108;
 static int const LOAD_CONST_313 = 83;
 static int const IMPORT_NAME_313 = 75;
 
+// OpCodes: Adapt for each Python version by checking the defines in the generated header opcode_ids.h
+// egrep '( LOAD_CONST | IMPORT_NAME )' opcode_ids.h
+
+static int constexpr LOAD_CONST_OpCode(long pyVersion)
+{
+    if (pyVersion >= 0x030E00) // 3.14
+        return 82;
+    if (pyVersion >= 0x030D00) // 3.13
+        return 83;
+    return 100;
+}
+
+static int constexpr IMPORT_NAME_OpCode(long pyVersion)
+{
+    if (pyVersion >= 0x030E00) // 3.14
+        return 73;
+    if (pyVersion >= 0x030D00) // 3.13
+        return 75;
+    return 108;
+}
+
 static bool isImportStar(PyObject *module)
 {
     // Find out whether we have a star import. This must work even
@@ -274,8 +296,9 @@ static bool isImportStar(PyObject *module)
     static PyObject *const _co_consts = Shiboken::String::createStaticString("co_consts");
     static PyObject *const _co_names = Shiboken::String::createStaticString("co_names");
 
-    static int LOAD_CONST = _PepRuntimeVersion() < 0x030D00 ? LOAD_CONST_312 : LOAD_CONST_313;
-    static int IMPORT_NAME = _PepRuntimeVersion() < 0x030D00 ? IMPORT_NAME_312 : IMPORT_NAME_313;
+
+    static const int LOAD_CONST = LOAD_CONST_OpCode(_PepRuntimeVersion());
+    static const int IMPORT_NAME = IMPORT_NAME_OpCode(_PepRuntimeVersion());
 
     auto *obFrame = reinterpret_cast<PyObject *>(PyEval_GetFrame());
     if (obFrame == nullptr)
@@ -542,10 +565,10 @@ SbkConverter **getTypeConverters(PyObject *module)
 // Replace the dictionary of a module by a different dict.
 // The dict should be filled with the content of the old dict, before.
 // Reason: Creating a module dict with __missing__ support.
-typedef struct {
+struct StartOf_PyModuleObject {
     PyObject_HEAD
     PyObject *md_dict;
-} StartOf_PyModuleObject;
+};
 
 bool replaceModuleDict(PyObject *module, PyObject *modClass, PyObject *dict)
 {
index 6053772cfd4711b4fcd554f0788ae55b7ba9af9b..d2570f48e7d8e78ef7bdeff5cdfe00071335605e 100644 (file)
@@ -23,7 +23,7 @@ bool checkIterableArgument(PyObject *obj)
 static PyObject *initPathLike()
 {
     PyObject *PathLike{};
-    auto osmodule = PyImport_ImportModule("os");
+    auto *osmodule = PyImport_ImportModule("os");
     if (osmodule == nullptr
         || (PathLike = PyObject_GetAttrString(osmodule, "PathLike")) == nullptr) {
         PyErr_Print();
index 0e48364c4aec70a4c330f93c749d3307d68cc192..d03feb2c7601757d09b03ce9f6b56e179d0bfb30 100644 (file)
@@ -13,6 +13,7 @@
 #include "helper.h"
 #include "pyobjectholder.h"
 #include "sbkarrayconverter.h"
+#include "sbkbindingutils.h"
 #include "sbkconverter.h"
 #include "sbkenum.h"
 #include "sbkerrors.h"
index 81f464cd3020889745a735e84782e83c3ed2e3e4..c3dee51adeab435b89b75af09e1ceaa2668e6cf3 100644 (file)
@@ -173,8 +173,8 @@ PyObject *GetSignature_Function(PyObject *obfunc, PyObject *modifier)
     if (props == nullptr)
         Py_RETURN_NONE;
 
-    int flags = PyCFunction_GET_FLAGS(obfunc);
-    PyObject *func_kind;
+    int flags = PyCFunction_GetFlags(obfunc);
+    PyObject *func_kind{};
     if (PyModule_Check(obtype_mod.object()))
         func_kind = PyName::function();
     else if (flags & METH_CLASS)
@@ -258,7 +258,7 @@ PyObject *get_signature_intern(PyObject *ob, PyObject *modifier)
 
 static PyObject *get_signature(PyObject * /* self */, PyObject *args)
 {
-    PyObject *ob;
+    PyObject *ob{};
     PyObject *modifier = nullptr;
 
     if (!PyArg_ParseTuple(args, "O|O", &ob, &modifier))
@@ -303,11 +303,18 @@ static PyObject *feature_import(PyObject * /* self */, PyObject *args, PyObject
     // feature_import did not handle it, so call the normal import.
     Py_DECREF(ret);
     static PyObject *builtins = PyEval_GetBuiltins();
-    PyObject *import_func = PyDict_GetItemString(builtins, "__orig_import__");
-    if (import_func == nullptr) {
+    PyObject *origImportFunc = PyDict_GetItemString(builtins, "__orig_import__");
+    if (origImportFunc == nullptr) {
         Py_FatalError("builtins has no \"__orig_import__\" function");
     }
-    ret = PyObject_Call(import_func, args, kwds);
+    // PYSIDE-3054: Instead of just calling the original import, we temporarily
+    //              reset the whole import function to the previous version.
+    //              This prevents unforeseen recursions like in settrace.
+    PyObject *featureImportFunc = PyDict_GetItemString(builtins, "__import__");
+    Py_INCREF(origImportFunc);
+    Py_INCREF(featureImportFunc);
+    PyDict_SetItemString(builtins, "__import__", origImportFunc);
+    ret = PyObject_Call(origImportFunc, args, kwds);
     if (ret) {
         // PYSIDE-2029: Intercept after the import to search for PySide usage.
         PyObject *post = PyObject_CallFunctionObjArgs(pyside_globals->feature_imported_func,
@@ -315,9 +322,12 @@ static PyObject *feature_import(PyObject * /* self */, PyObject *args, PyObject
         Py_XDECREF(post);
         if (post == nullptr) {
             Py_DECREF(ret);
-            return nullptr;
+            ret = nullptr;
         }
     }
+    PyDict_SetItemString(builtins, "__import__", featureImportFunc);
+    Py_DECREF(origImportFunc);
+    Py_DECREF(featureImportFunc);
     return ret;
 }
 
@@ -721,14 +731,14 @@ static PyObject *adjustFuncName(const char *func_name)
     // Finally, generate the correct path expression.
     char _buf[250 + 1] = {};
     if (prop_name) {
-        auto _prop_name = String::toCString(prop_name);
+        const auto *_prop_name = String::toCString(prop_name);
         if (is_class_prop)
             snprintf(_buf, sizeof(_buf), "%s.__dict__['%s'].fset", _path, _prop_name);
         else
             snprintf(_buf, sizeof(_buf), "%s.%s.fset", _path, _prop_name);
     }
     else {
-        auto _name = String::toCString(name);
+        const auto *_name = String::toCString(name);
         snprintf(_buf, sizeof(_buf), "%s.%s", _path, _name);
     }
     return String::fromCString(_buf);
index 04b411825de2f3fcc1080462664364ae40c62650..3ecddbf0125bc2cbb4d7f097ea49c2c07bc37651 100644 (file)
@@ -239,7 +239,7 @@ PyObject *_get_class_of_bm(PyObject *ob_bm)
 
 PyObject *_get_class_of_cf(PyObject *ob_cf)
 {
-    PyObject *selftype = PyCFunction_GET_SELF(ob_cf);
+    PyObject *selftype = PyCFunction_GetSelf(ob_cf);
     if (selftype == nullptr) {
         selftype = PyDict_GetItem(pyside_globals->map_dict, ob_cf);
         if (selftype == nullptr) {
index 2b69a1c6a0aea7b396bff8c5858ffba2dc45769d..e1b4b68947af45e5b7d6c1ab987e1e29c55ebbf3 100755 (executable)
@@ -27,4 +27,4 @@ def genpyi():
 
 
 if __name__ == "__main__":
-     main()
+    main()
index 600c431c9d93910592524388d16b80cfa3155d35..2d4cff2681519e8bfc3b4d700d1924ac2e4ea0ca 100644 (file)
@@ -1,4 +1,4 @@
-shiboken_library_soversion = str(@shiboken6_library_so_version@)
+shiboken_library_soversion = "@shiboken6_library_so_version@"
 
 version = "@FINAL_PACKAGE_VERSION@"
 version_info = (@shiboken_MAJOR_VERSION@, @shiboken_MINOR_VERSION@, @shiboken_MICRO_VERSION@, "@shiboken_PRE_RELEASE_VERSION_TYPE@", "@shiboken_PRE_RELEASE_VERSION@")
index 4a8ccdbc606f02d032fae3c3286694f8ac43a25f..e39dab542364eeff625956130a602d493b383a43 100644 (file)
@@ -157,10 +157,10 @@ def _mod_uses_pyside(module):
     try:
         source = inspect.getsource(module)
     except TypeError:
-        # this is a builtin module like sys
+        # This is a builtin module like sys.
         return False
     except OSError:
-        # this is a module withot source file
+        # This is a module without source file.
         return False
     except SyntaxError:
         # PYSIDE-2189: A UnicodeError happens in tokenize.py in find_cookie
@@ -168,9 +168,6 @@ def _mod_uses_pyside(module):
         # This is undocumented and a Python error, seen in Python 3.10.2 on Windows,
         # importing `pythoncom` of the win32 package.
         return False
-    except Exception:
-        # PYSIDE-2393: pytest behaves weird when allowing any other error.
-        return False
     return "PySide6" in source
 
 
index 65f52bde6c036e5f8b233d3188c9f4191372da78..8eea431c3bbfd3444ec7a61dfed9bc02ddeba31f 100644 (file)
@@ -17,19 +17,46 @@ This module configures the different layouts which can be used.
 It also implements them in this file. The configurations are
 used literally as strings like "signature", "existence", etc.
 """
-
+# flake8: noqa E:731
 import inspect
+import operator
+import sys
+import types
 import typing
 
+from functools import reduce
 from types import SimpleNamespace
 from textwrap import dedent
-from shibokensupport.signature.mapping import ellipsis, missing_optional_return
+from shibokensupport.signature.mapping import ellipsis, missing_optional_return, PlaceholderType
 from shibokensupport.signature.parser import using_snake_case
 from shibokensupport.signature import make_snake_case_name
+from collections.abc import Sequence, Iterable
 
 DEFAULT_PARAM_KIND = inspect.Parameter.POSITIONAL_ONLY
 
 
+def formatannotation(annotation, base_module=None):
+    if getattr(annotation, '__module__', None) == 'typing':
+        return repr(annotation).replace('typing.', '')
+    if isinstance(annotation, types.GenericAlias):
+        return str(annotation)
+    if isinstance(annotation, type):
+        if annotation.__module__ in ('builtins', base_module):
+            return annotation.__qualname__
+        return annotation.__module__ + '.' + annotation.__qualname__
+    return repr(annotation)
+
+
+# PYSIDE-3012: Patching Python < 3.9.8 or Python < 3.10.1
+def install_typing_patch():
+    v = sys.version_info[:3]
+    if v[1] == 9 and v[2] < 8 or v[1] == 10 and v[2] < 1:
+        inspect.formatannotation = formatannotation
+
+
+install_typing_patch()
+
+
 class SignatureLayout(SimpleNamespace):
     """
     Configure a signature.
@@ -101,78 +128,6 @@ typeerror = SignatureLayout(definition=False,
                             parameter_names=False)
 
 
-def define_nameless_parameter():
-    """
-    Create Nameless Parameters
-
-    A nameless parameter has a reduced string representation.
-    This is done by cloning the parameter type and overwriting its
-    __str__ method. The inner structure is still a valid parameter.
-    """
-    def __str__(self):
-        # for Python 2, we must change self to be an instance of P
-        klass = self.__class__
-        self.__class__ = P
-        txt = P.__str__(self)
-        self.__class__ = klass
-        txt = txt[txt.index(":") + 1:].strip() if ":" in txt else txt
-        return txt
-
-    P = inspect.Parameter
-    newname = "NamelessParameter"
-    bases = P.__bases__
-    body = dict(P.__dict__)  # get rid of mappingproxy
-    if "__slots__" in body:
-        # __slots__ would create duplicates
-        for name in body["__slots__"]:
-            del body[name]
-    body["__str__"] = __str__
-    return type(newname, bases, body)
-
-
-NamelessParameter = define_nameless_parameter()
-
-"""
-Note on the "Optional" feature:
-
-When an annotation has a default value that is None, then the
-type has to be wrapped into "typing.Optional".
-
-Note that only the None value creates an Optional expression,
-because the None leaves the domain of the variable.
-Defaults like integer values are ignored: They stay in the domain.
-
-That information would be lost when we use the "..." convention.
-
-Note that the typing module has the remarkable expansion
-
-    Optional[T]    is    Union[T, NoneType]
-
-We want to avoid that when generating the .pyi file.
-This is done by a regex in pyi_generator.py .
-The following would work in Python 3, but this is a version-dependent
-hack that also won't work in Python 2 and would be _very_ complex.
-"""
-# import sys
-# if sys.version_info[0] == 3:
-#     class hugo(list):pass
-#     typing._normalize_alias["hugo"] = "Optional"
-#     Optional = typing._alias(hugo, typing.T, inst=False)
-# else:
-#     Optional = typing.Optional
-
-
-def make_signature_nameless(signature):
-    """
-    Make a Signature Nameless
-
-    We use an existing signature and change the type of its parameters.
-    The signature looks different, but is totally intact.
-    """
-    for key in signature.parameters.keys():
-        signature.parameters[key].__class__ = NamelessParameter
-
-
 _POSITIONAL_ONLY         = inspect.Parameter.POSITIONAL_ONLY        # noqa E:201
 _POSITIONAL_OR_KEYWORD   = inspect.Parameter.POSITIONAL_OR_KEYWORD  # noqa E:201
 _VAR_POSITIONAL          = inspect.Parameter.VAR_POSITIONAL         # noqa E:201
@@ -180,9 +135,14 @@ _KEYWORD_ONLY            = inspect.Parameter.KEYWORD_ONLY           # noqa E:201
 _VAR_KEYWORD             = inspect.Parameter.VAR_KEYWORD            # noqa E:201
 _empty                   = inspect.Parameter.empty                  # noqa E:201
 
-
+# PYSIDE-3098: Iterable and Sequence can occur together in an overload.
+#              This needs sorting in order of generality.
+# This happened in the NumPy support of PySide 6.10. Note that the ordering
+# of methods there is completely different and unrelated to this mypy sorting.
 default_weights = {
     typing.Any: 1000,   # noqa E:241
+    Iterable:    500,   # noqa E:241
+    Sequence:    400,   # noqa E:241
     bool:        101,   # noqa E:241
     int:         102,   # noqa E:241
     float:       103,   # noqa E:241
@@ -190,6 +150,10 @@ default_weights = {
 }
 
 
+_ignore_mro = type, None, typing.Any, typing.TypeVar, typing.Type[PlaceholderType]
+mro_len = lambda ann: len(ann.mro()) if ann not in _ignore_mro and hasattr(ann, "mro") else 0
+
+
 def get_ordering_key(anno):
     """
     This is the main sorting algorithm for annotations.
@@ -206,6 +170,11 @@ def get_ordering_key(anno):
 
     A special case are numeric types, which have also an ordering between them.
     They can be handled separately, since they are all of the shortest mro.
+
+    PYSIDE-3012: For some reason, we failed to transform `Union[a, b]` directly
+                 into `a | b`. Something unknown about comparison must be different.
+                 Therefore the transform function was put on top.
+    XXX Get rid of the function and document the problem thoroughly.
     """
     typing_type = typing.get_origin(anno)
     is_union = typing_type is typing.Union
@@ -226,12 +195,12 @@ def get_ordering_key(anno):
             # Normal: Use the union arg with the shortest mro().
             leng = 9999
             for ann in typing_args:
-                lng = len(ann.mro())
+                lng = mro_len(ann)
                 if lng < leng:
                     leng = lng
                     anno = ann
     else:
-        leng = len(anno.mro()) if anno not in (type, None, typing.Any) else 0
+        leng = mro_len(anno)
         parts = 1
     if anno in default_weights:
         leng = - default_weights[anno]
@@ -258,23 +227,29 @@ def sort_by_inheritance(signatures):
     return signatures
 
 
-def best_to_remove(signatures, idx1, idx2, name=None):
+def best_to_remove(signatures, idx1, idx2, name):
     # Both have identical annotation.
     sig1 = signatures[idx1]
     sig2 = signatures[idx2]
     ra1 = sig1.return_annotation
     ra2 = sig2.return_annotation
-    # Both have equal return annotation.
+    # Both have equal return annotations
     if ra1 == ra2:
         for p1, p2 in zip(sig1.parameters.values(), sig2.parameters.values()):
-            # Use the first with a default.
+            # Keep the first with a default.
             if p1.default is not _empty or p2.default is not _empty:
-                return idx1 if p1.default is not _empty else idx2
-    # Use the one with a return annotation.
-    return idx1 if ra1 is not None else idx2
-
-
-def _remove_ambiguous_signatures_body(signatures, name=None):
+                # Note: We return what to remove!
+                return idx2 if p1.default is not _empty else idx1
+    if ra1 and ra2:
+        # Both have a return annotation.
+        # Remove the probably uglier of the two. This is likely to be the
+        # first one without effort because i.E. arg1 comes early in sorting.
+        return idx1
+    # Remove the one without a return annotation.
+    return idx1 if ra2 is not None else idx2
+
+
+def _remove_ambiguous_signatures_body(signatures, name):
     # By the sorting of signatures, duplicates will always be adjacent.
     last_ann = None
     last_idx = -1
@@ -299,36 +274,26 @@ def _remove_ambiguous_signatures_body(signatures, name=None):
     return True, new_sigs
 
 
-def is_inconsistent_overload(signatures):
-    count = 0
-    for sig in signatures:
-        count += 1 if "self" in sig.parameters else 0
-    return count != 0 and count != len(signatures)
-
-
-def remove_ambiguous_signatures(signatures, name=None):
+def remove_ambiguous_signatures(signatures, name):
     # This may run more than once because of indexing.
-    found, new_sigs = _remove_ambiguous_signatures_body(signatures)
+    found, new_sigs = _remove_ambiguous_signatures_body(signatures, name)
     while found:
-        found, new_sigs = _remove_ambiguous_signatures_body(new_sigs)
-    # Python cannot handle mixed methods and classmethods. Remove the latter.
-    if is_inconsistent_overload(signatures):
-        new_sigs = list(sig for sig in new_sigs if "self" in sig.parameters)
+        found, new_sigs = _remove_ambiguous_signatures_body(new_sigs, name)
     return new_sigs
 
 
-def create_signature(props, key):
+def create_signature_union(props, key):
     if not props:
         # empty signatures string
         return
     if isinstance(props["multi"], list):
         # multi sig: call recursively.
         # For debugging: Print the name!
-        name = props["multi"][0]["fullname"]
-        res = list(create_signature(elem, key) for elem in props["multi"])
+        name = props["multi"][0]["fullname"]
+        res = list(create_signature_union(elem, key) for elem in props["multi"])
         # PYSIDE-2846: Sort multi-signatures by inheritance in order to avoid shadowing.
         res = sort_by_inheritance(res)
-        res = remove_ambiguous_signatures(res, name=None)
+        res = remove_ambiguous_signatures(res, name)
         return res if len(res) > 1 else res[0]
 
     if type(key) is tuple:
@@ -361,7 +326,7 @@ def create_signature(props, key):
         del annotations["return"]
 
     # Build a signature.
-    kind = DEFAULT_PARAM_KIND
+    kind = last = DEFAULT_PARAM_KIND
     params = []
     snake_flag = using_snake_case()
 
@@ -384,7 +349,10 @@ def create_signature(props, key):
         if default is not _empty:
             if kind != _KEYWORD_ONLY:
                 kind = _POSITIONAL_OR_KEYWORD
+                if last == _VAR_POSITIONAL:
+                    kind = _KEYWORD_ONLY
         if default is None:
+            ann = typing.Union[ann]
             ann = typing.Optional[ann]
         if default is not _empty and layout.ellipsis:
             default = ellipsis
@@ -400,19 +368,49 @@ def create_signature(props, key):
                 continue
             if snake_flag:
                 name = make_snake_case_name(name)
+        last = kind
         param = inspect.Parameter(name, kind, annotation=ann, default=default)
         params.append(param)
 
     ret_anno = annotations.get('return', _empty)
     if ret_anno is not _empty and props["fullname"] in missing_optional_return:
+        ret_anno = typing.Union[ret_anno]
         ret_anno = typing.Optional[ret_anno]
-    sig = inspect.Signature(params,
-                            return_annotation=ret_anno,
-                            __validate_parameters__=False)
-
-    # the special case of nameless parameters
-    if not layout.parameter_names:
-        make_signature_nameless(sig)
-    return sig
+    return inspect.Signature(params, return_annotation=ret_anno,
+                             __validate_parameters__=False)
+
+
+def transform(signature):
+    # Change the annotations of the parameters to use "|" syntax.
+    params = []
+    changed = False
+    for idx, param in enumerate(signature.parameters.values()):
+        ann = param.annotation
+        if typing.get_origin(ann) is typing.Union:
+            args = typing.get_args(ann)
+            ann = reduce(operator.or_, args)
+            param = param.replace(annotation=ann)
+            changed = True
+        params.append(param)
+    ann = signature.return_annotation
+    if typing.get_origin(ann) is typing.Union:
+        args = typing.get_args(ann)
+        ann = reduce(operator.or_, args)
+        changed = True
+    return signature.replace(parameters=params, return_annotation=ann) if changed else signature
+
+
+def create_signature(props, key):
+    res = create_signature_union(props, key)
+    if type(res) is list:
+        for idx, sig in enumerate(res):
+            res[idx] = transform(sig)
+    else:
+        res = transform(res)
+    return res
+
+
+if sys.version_info[:2] < (3, 10):
+    create_signature = create_signature_union    # noqa F:811
 
 # end of file
index 2f9db0b6e53cc2b49e599601a7ba064f45017d1d..6f86df8c814e813badc0bb098f3d25875ffcf71d 100644 (file)
@@ -15,7 +15,7 @@ by producing a lot of clarity.
 import inspect
 import sys
 import types
-import typing
+import collections
 from shibokensupport.signature import get_signature as get_sig
 from shibokensupport.signature.layout import DEFAULT_PARAM_KIND
 from enum import Enum
@@ -44,6 +44,15 @@ def signal_check(thing):
     return thing and type(thing) in (Signal, SignalInstance)
 
 
+def is_inconsistent_overload(signatures):
+    if not isinstance(signatures, list):
+        return False
+    count = 0
+    for sig in signatures:
+        count += 1 if "self" in sig.parameters else 0
+    return count != 0 and count != len(signatures)
+
+
 class ExactEnumerator:
     """
     ExactEnumerator enumerates all signatures in a module as they are.
@@ -134,7 +143,7 @@ class ExactEnumerator:
                 name = base.__module__ + "." + name
             bases_list.append(name)
         bases_str = ', '.join(bases_list)
-        class_str = f"{class_name}({bases_str})"
+        class_str = f"{class_name}" if bases_str == "object" else f"{class_name}({bases_str})"
         # class_members = inspect.getmembers(klass)
         # gives us also the inherited things.
         class_members = sorted(list(klass.__dict__.items()))
@@ -270,15 +279,18 @@ class ExactEnumerator:
         if decorator in self.collision_track:
             decorator = f"builtins.{decorator}"
         signature = self.get_signature(func, decorator)
+        incon_err = False
+        if is_inconsistent_overload(signature):
+            incon_err = True
         # PYSIDE-2846: Special cases of signatures which inherit from object.
         _self = inspect.Parameter("self", DEFAULT_PARAM_KIND)
         if func_name == "__dir__":
-            signature = inspect.Signature([_self], return_annotation=typing.Iterable[str])
+            signature = inspect.Signature([_self], return_annotation=collections.abc.Iterable[str])
         elif func_name == "__repr__":
             signature = inspect.Signature([_self], return_annotation=str)
         if signature is not None:
             aug_ass = func in self.mypy_aug_ass_errors
-            with self.fmt.function(func_name, signature, decorator, aug_ass) as key:
+            with self.fmt.function(func_name, signature, decorator, aug_ass, incon_err) as key:
                 ret[key] = signature
         del self.func
         return ret
index cff54600562fcb1c863cfc05225a15e8848a7cc1..c5dc44644503e1aaab888c0112c56833914fccf5 100644 (file)
@@ -16,6 +16,7 @@ import argparse
 import inspect
 import io
 import logging
+import os
 import re
 import sys
 import typing
@@ -102,12 +103,22 @@ class Formatter(Writer):
     brace_searcher = re.compile(brace_pat, flags=re.VERBOSE)
     split = brace_searcher.split
 
+    @classmethod
+    def last_fixups(cls, source):
+        # PYSIDE-2517: findChild/findChildren type hints:
+        # PlaceholderType fix to avoid the '~' from TypeVar.__repr__
+        if "~PlaceholderType" in source:
+            source = source.replace("~PlaceholderType", "PlaceholderType")
+        # Replace all "NoneType" strings by "None" which is a typing convention.
+        return source.replace("NoneType", "None")
+
+    # To be removed when minimum version is 3.10:
     @classmethod
     def optional_replacer(cls, source):
         # PYSIDE-2517: findChild/findChildren type hints:
-        # PlaceHolderType fix to avoid the '~' from TypeVar.__repr__
-        if "~PlaceHolderType" in source:
-            source = source.replace("~PlaceHolderType", "PlaceHolderType")
+        # PlaceholderType fix to avoid the '~' from TypeVar.__repr__
+        if "~PlaceholderType" in source:
+            source = source.replace("~PlaceholderType", "PlaceholderType")
 
         while match := cls.opt_uni_searcher.search(source):
             start = match.start()
@@ -117,12 +128,15 @@ class Formatter(Writer):
             # Note: this list is interspersed with "," and surrounded by "", see parser.py
             parts = [x.strip() for x in cls.split(body) if x.strip() not in ("", ",")]
             if name == "typing.Optional":
-                parts.append("None")
+                parts.append("None ")
             res = " | ".join(parts)
             source = source[: start] + res + source[end :]
         # Replace all "NoneType" strings by "None" which is a typing convention.
         return source.replace("NoneType", "None")
 
+    if sys.version_info[:2] < (3, 10):
+        last_fixups = optional_replacer
+
     # self.level is maintained by enum_sig.py
     # self.is_method() is true for non-plain functions.
 
@@ -157,19 +171,25 @@ class Formatter(Writer):
         yield
 
     @contextmanager
-    def function(self, func_name, signature, decorator=None, aug_ass=None):
+    def function(self, func_name, signature, decorator=None, aug_ass=None, incon_err=None):
         if func_name == "__init__":
             self.print()
         key = func_name
         spaces = indent * self.level
         err_ignore = "  # type: ignore[misc]"
+        if incon_err:
+            err_ignore = "  # type: ignore[misc, overload-cannot-match]"
         if isinstance(signature, list):
             # PYSIDE-2846: Disable errors in augmented assignments.
-            opt_comment = (err_ignore if aug_ass else "")
+            opt_comment = (err_ignore if aug_ass or incon_err else "")
             for sig in signature:
                 self.print(f'{spaces}@typing.overload{opt_comment}')
-                opt_comment = ""
-                self._function(func_name, sig, spaces)
+                if incon_err:
+                    # need to mark all overloads
+                    pass
+                else:
+                    opt_comment = ""
+                self._function(func_name, sig, spaces, None, opt_comment)
         else:
             opt_comment = err_ignore if aug_ass else ""
             self._function(func_name, signature, spaces, decorator, opt_comment)
@@ -187,7 +207,7 @@ class Formatter(Writer):
         # the formatting with the inspect module explicitly removes the `typing` prefix.
         signature = self.fix_typing_prefix(signature)
         # from now on, the signature will be stringized.
-        signature = self.optional_replacer(signature)
+        signature = self.last_fixups(signature)
         self.print(f'{spaces}def {func_name}{signature}: ...{opt_comment}')
 
     @contextmanager
@@ -221,7 +241,7 @@ FROM_IMPORTS = [
     (None, ["os"]),
     (None, ["enum"]),
     (None, ["typing"]),
-    ("collections.abc", ["Iterable"]),
+    (None, ["collections.abc"]),
     ("PySide6.QtCore", ["PyClassProperty", "Signal", "SignalInstance"]),
     ("shiboken6", ["Shiboken"]),
     ]
@@ -238,7 +258,7 @@ def filter_from_imports(from_struct, text):
         for each in imports:
             # PYSIDE-1603: We search text that is a usage of the class `each`,
             #              but only if the class is not also defined here.
-            if (f"class {each}(") not in text:
+            if f"class {each}(" not in text and f"class {each}:" not in text:
                 if re.search(rf"(\b|@){each}\b([^\s\(:]|\n)", text):
                     lis.append(each)
                 # Search if a type is present in the return statement
@@ -332,7 +352,7 @@ def generate_pyi(import_name, outpath, options):
                 # We use it only in QtCore at the moment, but this
                 # could be extended to other modules. (must import QObject then)
                 if import_name == "PySide6.QtCore":
-                    wr.print("PlaceHolderType = typing.TypeVar(\"PlaceHolderType\", "
+                    wr.print("PlaceholderType = typing.TypeVar(\"PlaceholderType\", "
                              "bound=PySide6.QtCore.QObject)")
                 wr.print()
             else:
index af0fc6c0ae30405f1e14062b04bd3042ba23f888..4c20a111402d70b5209987d675406ba5f613a006 100644 (file)
@@ -18,6 +18,7 @@ import os
 import struct
 import sys
 import typing
+import collections
 
 from pathlib import Path
 from typing import TypeVar, Generic
@@ -29,16 +30,21 @@ class ellipsis:
         return "..."
 
 
+# PYSIDE-3135
+# Using _Self to avoid adding the parameter to typing in versions <3.11
+# Drop this when Python 3.11 is the minimum version
 if not hasattr(typing, "Self"):
     @typing._SpecialForm
     def Self(self, parameters):
         raise TypeError(f"{self} is not subscriptable")
-    typing.Self = Self
+    typing._Self = Self
+else:
+    typing._Self = typing.Self
 
 ellipsis = ellipsis()
 Point = typing.Tuple[int, int]
 Variant = typing.Any
-QImageCleanupFunction = typing.Callable[..., typing.Any]
+QImageCleanupFunction = collections.abc.Callable[..., typing.Any]
 
 # unfortunately, typing.Optional[t] expands to typing.Union[t, NoneType]
 # Until we can force it to create Optional[t] again, we use this.
@@ -47,7 +53,7 @@ NoneType = type(None)
 # PYSIDE-2517: findChild/findChildren type hints:
 # Placeholder so it does not trigger an UNDEFINED error while building.
 # Later it will be bound to a QObject, within the QtCore types extensions
-PlaceHolderType = TypeVar("PlaceHolderType")
+PlaceholderType = TypeVar("PlaceholderType")
 
 _S = TypeVar("_S")
 
@@ -223,16 +229,16 @@ type_map.update({
     "int": int,
     "List": ArrayLikeVariable,
     "Optional": typing.Optional,
-    "Iterable": typing.Iterable,
+    "Iterable": collections.abc.Iterable,
     "long": int,
     "long long": int,
     "nullptr": None,
-    "PyCallable": typing.Callable[..., typing.Any],
+    "PyCallable": collections.abc.Callable[..., typing.Any],
     "PyObject": object,
     "PyObject*": object,
     "PyArrayObject": ArrayLikeVariable(typing.Any),  # numpy
     "PyPathLike": typing.Union[str, bytes, os.PathLike[str]],
-    "PySequence": typing.Iterable,  # important for numpy
+    "PySequence": collections.abc.Iterable,  # important for numpy
     "PyTypeObject": type,
     "QChar": str,
     "QHash": typing.Dict,
@@ -281,6 +287,8 @@ type_map.update({
     "short": int,
     "signed char": int,
     "signed long": int,
+    "std.chrono.milliseconds": int,
+    "std.chrono.seconds": int,
     "std.list": typing.List,
     "std.map": typing.Dict,
     "std.nullptr_t": NoneType,
@@ -496,7 +504,7 @@ def init_PySide6_QtCore():
         "NULL": None,  # 5.6, MSVC
         "nullptr": None,  # 5.9
         # PYSIDE-2517: findChild/findChildren type hints:
-        "PlaceHolderType": typing.TypeVar("PlaceHolderType", bound=PySide6.QtCore.QObject),
+        "PlaceholderType": typing.TypeVar("PlaceholderType", bound=PySide6.QtCore.QObject),
         "PyBuffer": typing.Union[bytes, bytearray, memoryview],
         "PyByteArray": bytearray,
         "PyBytes": typing.Union[bytes, bytearray, memoryview],
@@ -543,6 +551,11 @@ def init_PySide6_QtCore():
     # Here we manually set it to map to 'str'.
     type_map_tuple.update({("PySide6.QtCore.QObject.setProperty", "char*"): str})
     type_map_tuple.update({("PySide6.QtCore.QObject.property", "char*"): str})
+    type_map_tuple.update({("PySide6.QtCore.QObject.inherits", "char*"): str})
+    type_map_tuple.update({("PySide6.QtCore.QObject.connect", "char*"): str})
+    type_map_tuple.update({("PySide6.QtCore.QObject.disconnect", "char*"): str})
+    type_map_tuple.update({("PySide6.QtCore.QObject.receivers", "char*"): str})
+    type_map_tuple.update({("PySide6.QtCore.qtTrId", "char*"): str})
 
     return locals()
 
@@ -740,6 +753,8 @@ missing_optional_return = {
     "PySide6.QtWidgets.QTableWidget.item",
     "PySide6.QtWidgets.QTableWidget.itemAt",
     "PySide6.QtWidgets.QTableWidget.mimeData",
+    "PySide6.QtWidgets.QTreeWidget.takeTopLevelItem",
+    "PySide6.QtWidgets.QTreeWidget.topLevelItem",
     "PySide6.QtWidgets.QWidget.childAt",
     "PySide6.QtWidgets.QWidget.find",
     "PySide6.QtWidgets.QWidget.focusProxy",
index 0f582fe24ae7d72487a800c1b6784448773aa9e1..17ee4441590acab4db1285210e21a93a9db48c6e 100644 (file)
@@ -10,6 +10,8 @@ import re
 import sys
 import typing
 import warnings
+import collections.abc
+import abc
 
 from types import SimpleNamespace
 from shibokensupport.signature.mapping import (type_map, type_map_tuple, update_mapping,
@@ -74,6 +76,9 @@ def _get_flag_enum_option():
     # _PepUnicode_AsString: Fix a broken promise
     if pyminver and pyminver >= (3, 10):
         warnings.warn(f"{p} _PepUnicode_AsString can now be replaced by PyUnicode_AsUTF8! ***")
+    # PYSIDE-3012: Emit a warning when we may simplify layout.py and pyi_generator.py
+    if pyminver and pyminver >= (3, 10):
+        warnings.warn(f"{p} layout.py and pyi_generator.py can now remove old code! ***")
     # PYSIDE-1960: Emit a warning when we may remove bufferprocs_py37.(cpp|h)
     if pyminver and pyminver >= (3, 11):
         warnings.warn(f"{p} The files bufferprocs_py37.(cpp|h) should be removed ASAP! ***")
@@ -107,21 +112,21 @@ def dprint(*args, **kw):
             sys.stdout.flush()
 
 
-_cache = {}
+class ArglistParser:
+    def __init__(self):
+        regex = build_brace_pattern(level=3, separators=",")
+        rec = re.compile(regex, flags=re.VERBOSE)
+        self._finditer = rec.finditer
+
+    def parse(self, argstr):
+        return list(x.group(1).strip() for x in self._finditer(argstr))
+
+
+arglistParser = ArglistParser()
 
 
 def _parse_arglist(argstr):
-    # The following is a split re. The string is broken into pieces which are
-    # between the recognized strings. Because the re has groups, both the
-    # strings and the separators are returned, where the strings are not
-    # interesting at all: They are just the commata.
-    key = "_parse_arglist"
-    if key not in _cache:
-        regex = build_brace_pattern(level=3, separators=",")
-        _cache[key] = re.compile(regex, flags=re.VERBOSE)
-    split = _cache[key].split
-    # Note: this list is interspersed with "," and surrounded by ""
-    return [x.strip() for x in split(argstr) if x.strip() not in ("", ",")]
+    return arglistParser.parse(argstr)
 
 
 def _parse_line(line):
@@ -243,7 +248,7 @@ def get_name(thing):
 
 def _resolve_value(thing, valtype, line):
     if thing in ("0", "None") and valtype:
-        if valtype.startswith("PySide6.") or valtype.startswith("typing."):
+        if valtype.startswith(("PySide6.", "typing.", "collections.abc.")):
             return None
         mapped = type_map.get(valtype)
         # typing.Any: '_SpecialForm' object has no attribute '__name__'
@@ -297,13 +302,17 @@ def to_string(thing):
     # so we fall back to use __name__ before the next condition.
     if isinstance(thing, typing.TypeVar):
         return get_name(thing)
-    if hasattr(thing, "__name__") and thing.__module__ != "typing":
+    if hasattr(thing, "__name__") and thing.__module__ not in ("typing", "collections.abc"):
         m = thing.__module__
         dot = "." in str(thing) or m not in (thing.__qualname__, "builtins")
         name = get_name(thing)
         ret = m + "." + name if dot else name
         assert (eval(ret, globals(), namespace))
         return ret
+    elif type(thing) in (abc.ABCMeta, type):
+        # collections.abc.Sequence without argument is very different from typing.
+        # PYSIDE-3147 - type(typing.Union) is a type, not typing._SpecialForm.
+        return f"{thing.__module__}.{thing.__name__}"
     # Note: This captures things from the typing module:
     return str(thing)
 
@@ -328,7 +337,7 @@ def get_from_another_module(thing):
                 type_map[thing] = res
                 return res
             except AttributeError:
-                # Maybe it was anothr module...
+                # Maybe it was another module...
                 pass
     return None
 
@@ -411,7 +420,7 @@ def handle_argvar(obj):
     Currently, the best approximation is types.Sequence.
     We want to change that to types.Iterable in the near future.
     """
-    return _handle_generic(obj, typing.Sequence)
+    return _handle_generic(obj, collections.abc.Sequence)
 
 
 def handle_retvar(obj):
@@ -441,7 +450,11 @@ def calculate_props(line):
             ann = 'nullptr'     # maps to None
             tup = name, ann
             arglist[idx] = tup
-        annotations[name] = _resolve_type(ann, line, 0, handle_argvar, parsed.funcname)
+        # When the variable name from constructor and the property is the same,
+        # one overwrites the other. This is fixed here and the latter is omitted.
+        # The property variables added in layout.py
+        if name not in annotations:
+            annotations[name] = _resolve_type(ann, line, 0, handle_argvar, parsed.funcname)
         if len(tup) == 3:
             default = _resolve_value(tup[2], ann, line)
             # PYSIDE-2846: When creating signatures, the defaults should be hashable.
@@ -491,7 +504,7 @@ def fix_variables(props, line):
     for idx, name in enumerate(varnames):
         ann = safe_annos[name]
         if isinstance(ann, ArrayLikeVariable):
-            ann = typing.Sequence[ann.type]
+            ann = collections.abc.Sequence[ann.type]
             annos[name] = ann
         if not isinstance(ann, ResultVariable):
             continue
index eb876634ca5def59c2c56b02d8f580752d23d821..c368579582d8950447267376f85c5aecc424db8d 100644 (file)
@@ -5,16 +5,16 @@
 #include <parser/codemodel.h>
 #include <clangparser/compilersupport.h>
 
-#include <QtCore/QCoreApplication>
-#include <QtCore/QCommandLineOption>
-#include <QtCore/QCommandLineParser>
-#include <QtCore/QDateTime>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QLibraryInfo>
-#include <QtCore/QVersionNumber>
-#include <QtCore/QXmlStreamWriter>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qcommandlineoption.h>
+#include <QtCore/qcommandlineparser.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qversionnumber.h>
+#include <QtCore/qxmlstream.h>
 
 #include <iostream>
 #include <algorithm>
index ac0ef05119dbee47af8a58f70e162f905b40e2ae..fc98d404a0970d905bf919519f035a53574cfe2a 100644 (file)
@@ -31,6 +31,7 @@ mapuser.cpp mapuser.h
 modelindex.h
 modifications.cpp modifications.h
 modified_constructor.cpp modified_constructor.h
+moveonly.h
 multiple_derived.cpp multiple_derived.h
 noimplicitconversion.h
 nondefaultctor.h
index 720d0ed9646f2d31f8a23135243a772da1602623..722207613b7bc8cdc72ed8640dcd9646db49655f 100644 (file)
@@ -7,3 +7,8 @@ void DerivedUsingCt::foo()
 {
     delete new DerivedUsingCt(42);
 }
+
+int DerivedUsingCt::derivedValue() const
+{
+    return m_derivedValue;
+}
index 6bc026d089f59bfd5ec2ade71209a5b87dc479c5..758e52937a77796053d92e1ff40637d9df3bee43 100644 (file)
@@ -13,5 +13,9 @@ public:
     using CtParam::CtParam;
 
     void foo();
+    int derivedValue() const;
+
+private:
+    int m_derivedValue = 37;
 };
 #endif // DERIVEDUSINGCT_H
diff --git a/sources/shiboken6/tests/libsample/moveonly.h b/sources/shiboken6/tests/libsample/moveonly.h
new file mode 100644 (file)
index 0000000..69d155f
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef MOVEONLY_H
+#define MOVEONLY_H
+
+#include "libsamplemacros.h"
+
+class MoveOnly
+{
+public:
+    LIBMINIMAL_DISABLE_COPY(MoveOnly)
+    LIBMINIMAL_DEFAULT_MOVE(MoveOnly)
+
+    explicit MoveOnly(int v = 0) noexcept : m_value(v) {}
+    ~MoveOnly() = default;
+
+    int value() const { return m_value; }
+
+private:
+    int m_value;
+};
+
+class MoveOnlyHandler
+{
+public:
+    LIBMINIMAL_DISABLE_COPY(MoveOnlyHandler)
+    LIBMINIMAL_DISABLE_MOVE(MoveOnlyHandler)
+
+    MoveOnlyHandler() noexcept = default;
+    virtual ~MoveOnlyHandler() = default;
+
+    static MoveOnly passMoveOnly(MoveOnly m) { return m; }
+
+    // Test whether compilation succeeds
+    virtual MoveOnly passMoveOnlyVirtually(MoveOnly m) { return m; }
+};
+
+#endif // MOVEONLY_H
index eae5af2d23c047325f039b42b7640c70ffe14803..18a18d28d7a438553d314a91ead7cd71b2d24be4 100644 (file)
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "samplenamespace.h"
+#include "moveonly.h"
 
 #include <iostream>
 #include <cstdlib>
index ade1c8bad8709ee8193e2312d23c1ae7fa51fa6d..83d7eced3890ceb5e543cf8277cf2cdbd4a9e3be 100644 (file)
@@ -11,7 +11,7 @@
     <value-type name="Number" />
 
     <smart-pointer-type name="SharedPtr" type="shared" getter="data" ref-count-method="useCount"
-                        instantiations="Str"/>
+                        excluded-instantiations="Integer,int"/>
     <value-type name="SmartPtrTester"/>
 
     <typedef-type name="ValueWithUnitIntInch" source="ValueWithUnit&lt;int,LengthUnit::Inch&gt;"/>
index 11b22f0389c64eedb70baa0b43fd917dc101a305..4a200becd2278768d1d7d84196a5fc1fd400d316 100644 (file)
@@ -18,6 +18,7 @@ set(qtxmltosphinx_SRC
     ${generator_src_dir}/qtdoc/qtxmltosphinx.cpp
     ${api_extractor_src_dir}/codesniphelpers.cpp
     ${api_extractor_src_dir}/textstream.cpp
+    ${api_extractor_src_dir}/filecache.cpp
     main.cpp)
 
 add_executable(qtxmltosphinx ${qtxmltosphinx_SRC})
index 5b0624376c6b8c78769f7bb2f0ed544d8f944f69..b2a678525b274a7ed3ff22fe46027226d95c66bb 100644 (file)
@@ -4,11 +4,11 @@
 #include "qtxmltosphinxinterface.h"
 #include "qtxmltosphinx.h"
 
-#include <QtCore/QCommandLineParser>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QDebug>
-#include <QtCore/QFile>
-#include <QtCore/QLoggingCategory>
+#include <QtCore/qcommandlineparser.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qloggingcategory.h>
 
 #include <exception>
 #include <iostream>
index 25074e716f3c33d7cd6b9db21f121f766c14856c..109ab288e0d30e5d9db3e8fd6a31a61c0b169b6a 100644 (file)
@@ -17,6 +17,7 @@ set(qtxmltosphinxtest_SRC
     ${generator_src_dir}/qtdoc/qtxmltosphinx.cpp
     ${api_extractor_src_dir}/codesniphelpers.cpp
     ${api_extractor_src_dir}/textstream.cpp
+    ${api_extractor_src_dir}/filecache.cpp
     qtxmltosphinxtest.cpp
     qtxmltosphinxtest.h)
 
index a06f752bbeca5b70089c51616b1f3c06470bea93..5153c998f2fcfd018c93447946d9c746a9ce3c0c 100644 (file)
@@ -3,11 +3,14 @@
 
 #include "qtxmltosphinxtest.h"
 #include "qtxmltosphinx.h"
-#include <QtTest/QTest>
 
-#include <QtCore/QBuffer>
-#include <QtCore/QDebug>
-#include <QtCore/QLoggingCategory>
+#include <QtTest/qtest.h>
+
+#include <QtCore/qbuffer.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qtemporaryfile.h>
 
 using namespace Qt::StringLiterals;
 
@@ -503,7 +506,13 @@ void QtXmlToSphinxTest::testSnippetExtraction()
     QBuffer buffer(&file);
     QVERIFY(buffer.open(QIODevice::ReadOnly));
     QString errorMessage;
-    QString actual = QtXmlToSphinx::readSnippet(buffer, id, &errorMessage);
+
+    QTemporaryFile snippetFile(QDir::tempPath() + "/XXXXXX_snippet.txt"_L1);
+    QVERIFY(snippetFile.open());
+    snippetFile.write(file);
+    const QString fileName = snippetFile.fileName();
+    snippetFile.close();
+    QString actual = QtXmlToSphinx::readSnippet(fileName, id, &errorMessage);
     QVERIFY2(errorMessage.isEmpty(), qPrintable(errorMessage));
     QCOMPARE(actual, expected);
 }
index 0a210b7a0ef1422ff731b3819655c08187fcfe9d..535ae181773bcfd0a4d5b2c50a1df11c0f995d9a 100644 (file)
@@ -6,7 +6,7 @@
 
 #include "qtxmltosphinxinterface.h"
 
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
 
 class QtXmlToSphinxTest : public QObject, public QtXmlToSphinxDocGeneratorInterface
 {
index f693008b08d77c50f7cf1cd6698c799f9b93dd6a..54bdc419c2755ccdac5249eabb5544de64e57347 100644 (file)
@@ -59,6 +59,8 @@ ${CMAKE_CURRENT_BINARY_DIR}/sample/mderived5_wrapper.cpp
 ${CMAKE_CURRENT_BINARY_DIR}/sample/modelindex_wrapper.cpp
 ${CMAKE_CURRENT_BINARY_DIR}/sample/modifications_wrapper.cpp
 ${CMAKE_CURRENT_BINARY_DIR}/sample/modifiedconstructor_wrapper.cpp
+${CMAKE_CURRENT_BINARY_DIR}/sample/moveonly_wrapper.cpp
+${CMAKE_CURRENT_BINARY_DIR}/sample/moveonlyhandler_wrapper.cpp
 ${CMAKE_CURRENT_BINARY_DIR}/sample/noimplicitconversion_wrapper.cpp
 ${CMAKE_CURRENT_BINARY_DIR}/sample/nondefaultctor_wrapper.cpp
 ${CMAKE_CURRENT_BINARY_DIR}/sample/objectmodel_wrapper.cpp
index b251ce8c7bccda8dd8c3b3e0712804a295326e03..b37e28de364a8501043bb194a133001e0f48892f 100644 (file)
@@ -131,6 +131,7 @@ class DerivedTest(unittest.TestCase):
         '''Test whether a constructor of the base class declared by using works'''
         obj = DerivedUsingCt(42)
         self.assertEqual(obj.value(), 42)
+        self.assertEqual(obj.derivedValue(), 37)
 
     def testVirtualWithOutParameter(self):
         d = Derived()
index 64806417a135c6803bb9c29c0cfba2c1381b994b..1de25224ff52fb8d730cc2b45c09ded299725009 100644 (file)
@@ -27,6 +27,7 @@
 #include "modelindex.h"
 #include "modifications.h"
 #include "modified_constructor.h"
+#include "moveonly.h"
 #include "multiple_derived.h"
 #include "noimplicitconversion.h"
 #include "nondefaultctor.h"
index 73866e3a2ce6ce78d60ebeba565a70d59260ee63..aef14b39a3095964e64d4572c5694edf974df6fd 100644 (file)
@@ -52,19 +52,19 @@ class ObjectTypeTest(unittest.TestCase):
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testParentFromCpp(self):
         o = ObjectType()
-        self.assertEqual(sys.getrefcount(o), 2)
+        base_count = sys.getrefcount(o)  # 1 from 3.14 onwards, previously 2
         o.getCppParent().setObjectName('parent')
-        self.assertEqual(sys.getrefcount(o), 3)
+        self.assertEqual(sys.getrefcount(o), base_count + 1)
         o.getCppParent().setObjectName('parent')
-        self.assertEqual(sys.getrefcount(o), 3)
+        self.assertEqual(sys.getrefcount(o), base_count + 1)
         o.getCppParent().setObjectName('parent')
-        self.assertEqual(sys.getrefcount(o), 3)
+        self.assertEqual(sys.getrefcount(o), base_count + 1)
         o.getCppParent().setObjectName('parent')
-        self.assertEqual(sys.getrefcount(o), 3)
+        self.assertEqual(sys.getrefcount(o), base_count + 1)
         o.getCppParent().setObjectName('parent')
-        self.assertEqual(sys.getrefcount(o), 3)
+        self.assertEqual(sys.getrefcount(o), base_count + 1)
         o.destroyCppParent()
-        self.assertEqual(sys.getrefcount(o), 2)
+        self.assertEqual(sys.getrefcount(o), base_count)
 
     def testNextInFocusChainCycle(self):
         parent = ObjectType()
index 9f6012164b478742ea55b9d029a3c349b9f7dd69..43b900e89321bf582f184ed45f85da2f12341954 100644 (file)
@@ -28,9 +28,9 @@ class ObjectTypeLayoutTest(unittest.TestCase):
         o1 = ObjectType(lt)
         o1.setObjectName('o1')
 
-        self.assertEqual(sys.getrefcount(o1), 3)
+        base_ref_count = sys.getrefcount(o1)
         lt.takeChild('o1')
-        self.assertEqual(sys.getrefcount(o1), 2)
+        self.assertEqual(sys.getrefcount(o1), base_ref_count - 1)
 
     def testSetNullLayout(self):
         '''ObjectType.setLayout(0).'''
index ecb291123779641cbe57115e6940293010fc52d0..72a9c908ac5aeabf153bc63643528fddc8bc66fe 100644 (file)
@@ -45,11 +45,11 @@ class ReparentingTest(unittest.TestCase):
         '''Set the same parent twice to check if the ref continue the same'''
         obj = ObjectType()
         parent = ObjectType()
-        self.assertEqual(sys.getrefcount(obj), 2)
+        base_ref_count = sys.getrefcount(obj)
         obj.setParent(parent)
-        self.assertEqual(sys.getrefcount(obj), 3)
+        self.assertEqual(sys.getrefcount(obj), base_ref_count + 1)
         obj.setParent(parent)
-        self.assertEqual(sys.getrefcount(obj), 3)
+        self.assertEqual(sys.getrefcount(obj), base_ref_count + 1)
 
     def testReparentedExtObjectType(self):
         '''Reparent children from one extended parent to another.'''
index 4bfe125cd896a4081c7e939f676d15042fd656e8..b3e2356881c21cfa532e629896cd00af3361fbe0 100644 (file)
@@ -31,6 +31,7 @@ from sample import IntArray2, VirtualMethods
 from shibokensupport.signature import get_signature
 
 import typing
+import collections
 
 
 class PointerPrimitiveTypeTest(unittest.TestCase):
@@ -45,7 +46,7 @@ class PointerPrimitiveTypeTest(unittest.TestCase):
         self.assertTrue(found)
         ann = sig.parameters["data"].annotation
         self.assertEqual(ann.__args__, (int,))
-        self.assertTrue(issubclass(ann.__origin__, typing.Iterable))
+        self.assertTrue(issubclass(ann.__origin__, collections.abc.Iterable))
 
     def testReturnVarSignature(self):
         # signature="getMargins(int*,int*,int*,int*)const">
index 319a564b45ae043f02244e1bc184f1c74ae97cb1..e699ffd2f5dbd159d8ec458df4773a8197c1e000 100644 (file)
@@ -15,7 +15,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
 from shiboken_paths import init_paths
 init_paths()
 
-from sample import cacheSize
+from sample import wrapperCount
 from sample import ProtectedNonPolymorphic, ProtectedVirtualDestructor
 from sample import (ProtectedPolymorphic, ProtectedPolymorphicDaughter,
                     ProtectedPolymorphicGrandDaughter)
@@ -68,7 +68,7 @@ class ProtectedNonPolymorphicTest(unittest.TestCase):
     def tearDown(self):
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
-        self.assertEqual(cacheSize(), 0)
+        self.assertEqual(wrapperCount(), 0)
 
     def testProtectedCall(self):
         '''Calls a non-virtual protected method.'''
@@ -98,7 +98,7 @@ class ProtectedPolymorphicTest(unittest.TestCase):
     def tearDown(self):
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
-        self.assertEqual(cacheSize(), 0)
+        self.assertEqual(wrapperCount(), 0)
 
     def testProtectedCall(self):
         '''Calls a virtual protected method.'''
@@ -149,7 +149,7 @@ class ProtectedPolymorphicGrandDaugherTest(unittest.TestCase):
     def tearDown(self):
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
-        self.assertEqual(cacheSize(), 0)
+        self.assertEqual(wrapperCount(), 0)
 
     def testProtectedCallWithInstanceCreatedOnCpp(self):
         '''Calls a virtual protected method from parent class on an instance created in C++.'''
@@ -176,7 +176,7 @@ class ProtectedVirtualDtorTest(unittest.TestCase):
     def tearDown(self):
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
-        self.assertEqual(cacheSize(), 0)
+        self.assertEqual(wrapperCount(), 0)
 
     def testVirtualProtectedDtor(self):
         '''Original protected virtual destructor is being called.'''
@@ -232,7 +232,7 @@ class ProtectedEnumTest(unittest.TestCase):
     def tearDown(self):
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
-        self.assertEqual(cacheSize(), 0)
+        self.assertEqual(wrapperCount(), 0)
 
     def testProtectedMethodWithProtectedEnumArgument(self):
         '''Calls protected method with protected enum argument.'''
@@ -313,7 +313,7 @@ class ProtectedPropertyTest(unittest.TestCase):
         del self.obj
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
-        self.assertEqual(cacheSize(), 0)
+        self.assertEqual(wrapperCount(), 0)
 
     def testProtectedProperty(self):
         '''Writes and reads a protected integer property.'''
@@ -342,7 +342,7 @@ class ProtectedPropertyTest(unittest.TestCase):
 
     def testProtectedValueTypePropertyWrapperRegistration(self):
         '''Access colocated protected value type property.'''
-        cache_size = cacheSize()
+        wrapper_count = wrapperCount()
         point = Point(12, 34)
         obj = createProtectedProperty()
         obj.protectedValueTypeProperty
@@ -356,7 +356,7 @@ class ProtectedPropertyTest(unittest.TestCase):
         del obj, point, pointProperty
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
-        self.assertEqual(cacheSize(), cache_size)
+        self.assertEqual(wrapperCount(), wrapper_count)
 
     def testProtectedValueTypePointerProperty(self):
         '''Writes and reads a protected value type pointer property.'''
@@ -385,7 +385,7 @@ class PrivateDtorProtectedMethodTest(unittest.TestCase):
     def tearDown(self):
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
-        self.assertEqual(cacheSize(), 0)
+        self.assertEqual(wrapperCount(), 0)
 
     def testProtectedMethod(self):
         '''Calls protected method of a class with a private destructor.'''
index 2e11bebe41f92024a15eb61cb295a64d6bdfb919..c003ad3984114cc95a91e5bf8d671dbc9ac37c93 100644 (file)
@@ -72,6 +72,13 @@ class ModuleTest(unittest.TestCase):
         # o should be moved from, name is now empty
         self.assertEqual(len(o.objectName()), 0)
 
+    def testMoveOnlyTypes(self):
+        """Pass a move only type (convert using std::move())."""
+        v = 42
+        mo = sample.MoveOnly(v)
+        mo2 = sample.MoveOnlyHandler.passMoveOnly(mo)
+        self.assertEqual(mo2.value(), v)
+
 
 if __name__ == '__main__':
     unittest.main()
index 43e6b08de5d28bf290c81fd83eccb37022c26e8a..a6cd6e055b5e0d49770b3a67ed397eb0cb237262 100644 (file)
@@ -1,6 +1,82 @@
 // Copyright (C) 2022 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
+// @snippet module-helpers
+static bool Check2TupleOfNumbers(PyObject *pyIn)
+{
+    if (PySequence_Check(pyIn) == 0 || PySequence_Size(pyIn) != 2)
+        return false;
+    Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0));
+    Shiboken::AutoDecRef pyImag(PySequence_GetItem(pyIn, 1));
+    return PyNumber_Check(pyReal) != 0 && PyNumber_Check(pyImag) != 0;
+}
+
+static bool checkPyCapsuleOrPyCObject(PyObject *pyObj)
+{
+    return PyCapsule_CheckExact(pyObj) != 0;
+}
+
+static PyObject* __convertCppValuesToPython(const char **typeNames, void **values, Py_ssize_t size)
+{
+    PyObject* result = PyTuple_New(size);
+    for (Py_ssize_t i = 0; i < size; ++i) {
+        Shiboken::Conversions::SpecificConverter converter(typeNames[i]);
+        PyTuple_SetItem(result, i, converter.toPython(values[i]));
+    }
+    return result;
+}
+// @snippet module-helpers
+
+// @snippet getConversionTypeString
+Shiboken::Conversions::SpecificConverter converter(%1);
+const char *%0 = nullptr;
+switch (converter.conversionType()) {
+case Shiboken::Conversions::SpecificConverter::CopyConversion:
+    %0 = "Copy conversion";
+    break;
+case Shiboken::Conversions::SpecificConverter::PointerConversion:
+    %0 = "Pointer conversion";
+    break;
+case Shiboken::Conversions::SpecificConverter::ReferenceConversion:
+    %0 = "Reference conversion";
+    break;
+default:
+    %0 = "Invalid conversion";
+    break;
+}
+%PYARG_0 = %CONVERTTOPYTHON[const char*](%0);
+// @snippet getConversionTypeString
+
+// @snippet convertValueTypeToCppAndThenToPython
+const char *typeNames[] = { "Point", "Point*", "Point&" };
+void *values[] = { &%1, &%2, &(%3) };
+%PYARG_0 = __convertCppValuesToPython(typeNames, values, 3);
+// @snippet convertValueTypeToCppAndThenToPython
+
+// @snippet convertValueTypeToCppAndThenToPython
+const char *typeNames[] = { "Point", "Point*", "Point&" };
+void *values[] = { &%1, &%2, &%3 };
+%PYARG_0 = __convertCppValuesToPython(typeNames, values, 3);
+// @snippet convertValueTypeToCppAndThenToPython
+
+// @snippet convertObjectTypeToCppAndThenToPython
+const char *typeNames[] = { "ObjectType*", "ObjectType&" };
+void *values[] = { &%1, &%2 };
+%PYARG_0 = __convertCppValuesToPython(typeNames, values, 2);
+// @snippet convertObjectTypeToCppAndThenToPython
+
+// @snippet convertListOfIntegersToCppAndThenToPython
+const char *typeNames[] = { "std::list<int>" };
+void *values[] = { &%1 };
+%PYARG_0 = __convertCppValuesToPython(typeNames, values, 1);
+// @snippet convertListOfIntegersToCppAndThenToPython
+
+// @snippet convertIntegersToCppAndThenToPython
+const char *typeNames[] = { "int", "int" };
+void *values[] = { &%1, &%2 };
+%PYARG_0 = __convertCppValuesToPython(typeNames, values, 2);
+// @snippet convertIntegersToCppAndThenToPython
+
 // @snippet intwrapper_add_ints
 extern "C" {
 static PyObject *Sbk_IntWrapper_add_ints(PyObject * /* self */, PyObject *args)
@@ -52,3 +128,177 @@ static PyObject *Sbk_IntWrapper_add_ints(PyObject * /* self */, PyObject *args)
     %0 = new %TYPE(width, height);
 }
 // @snippet size_char_ct
+
+// @snippet nonConversionRuleForArgumentWithDefaultValue
+ObjectType *tmpObject = nullptr;
+%BEGIN_ALLOW_THREADS
+%RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(&tmpObject);
+%END_ALLOW_THREADS
+%PYARG_0 = PyTuple_New(2);
+PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](%0));
+PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[ObjectType*](tmpObject));
+// @snippet nonConversionRuleForArgumentWithDefaultValue
+
+// @snippet reparent-layout-items
+static void reparent_layout_items(PyObject *parent, PyObject *layout)
+{
+    // CHECKTYPE and ISCONVERTIBLE are used here for test purposes, don't change them.
+    if (!%CHECKTYPE[ObjectTypeLayout*](layout) && !%ISCONVERTIBLE[ObjectTypeLayout*](layout))
+        return;
+    /* %CHECKTYPE[ObjectTypeLayout*](layout) */
+    /* %ISCONVERTIBLE[ObjectTypeLayout*](layout) */
+    ObjectTypeLayout *var;
+    var = %CONVERTTOCPP[ObjectTypeLayout*](layout);
+    // TODO-CONVERTER: erase this
+    // ObjectTypeLayout* var2 = %CONVERTTOCPP[ObjectTypeLayout*](layout);
+    const ObjectTypeList &objChildren = var->objects();
+    for (auto *child : objChildren) {
+        if (child->isLayoutType()) {
+            auto *childLayout = reinterpret_cast<ObjectTypeLayout*>(child);
+            reparent_layout_items(parent, %CONVERTTOPYTHON[ObjectTypeLayout*](childLayout));
+            Shiboken::Object::setParent(layout, %CONVERTTOPYTHON[ObjectTypeLayout*](childLayout));
+        } else {
+            Shiboken::Object::setParent(parent, %CONVERTTOPYTHON[ObjectType*](child));
+        }
+    }
+}
+// @snippet reparent-layout-items
+
+// @snippet fix-margins-parameters
+int a0, a1, a2, a3;
+%BEGIN_ALLOW_THREADS
+%CPPSELF->::%TYPE::%FUNCTION_NAME(&a0, &a1, &a2, &a3);
+%END_ALLOW_THREADS
+%PYARG_0 = PyTuple_New(4);
+PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[int](a0));
+PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[int](a1));
+PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[int](a2));
+PyTuple_SetItem(%PYARG_0, 3, %CONVERTTOPYTHON[int](a3));
+// @snippet fix-margins-parameters
+
+// @snippet fix-margins-return
+PyObject *obj = %PYARG_0.object();
+bool ok = false;
+if (PySequence_Check(obj) != 0 && PySequence_Size(obj) == 4) {
+    Shiboken::AutoDecRef m0(PySequence_GetItem(obj, 0));
+    Shiboken::AutoDecRef m1(PySequence_GetItem(obj, 1));
+    Shiboken::AutoDecRef m2(PySequence_GetItem(obj, 2));
+    Shiboken::AutoDecRef m3(PySequence_GetItem(obj, 3));
+    ok = PyNumber_Check(m0) != 0 && PyNumber_Check(m1) != 0
+         && PyNumber_Check(m2) && PyNumber_Check(m3) != 0;
+    if (ok) {
+        *%1 = %CONVERTTOCPP[int](m0);
+        *%2 = %CONVERTTOCPP[int](m1);
+        *%3 = %CONVERTTOCPP[int](m2);
+        *%4 = %CONVERTTOCPP[int](m3);
+    }
+}
+if (!ok) {
+    PyErr_SetString(PyExc_TypeError, "Sequence of 4 numbers expected");
+    %1 = %2 = %3 = %4 = 0;
+}
+// @snippet fix-margins-return
+
+// @snippet sumArrayAndLength
+bool ok = false;
+if (PySequence_Check(%PYARG_1) != 0) {
+    if (int *array = Shiboken::sequenceToIntArray(%PYARG_1, true)) {
+        ok = PyErr_Occurred() == nullptr;
+        if (ok) {
+            %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(array);
+            %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+        }
+        delete [] array;
+    }
+}
+if (!ok)
+    PyErr_SetString(PyExc_TypeError, "Should be a sequence of ints");
+// @snippet sumArrayAndLength
+
+// @snippet callArrayMethod
+const Py_ssize_t numItems = PySequence_Size(%PYARG_1);
+Shiboken::ArrayPointer<int> cppItems(numItems);
+for (Py_ssize_t i = 0; i < numItems; i++) {
+    Shiboken::AutoDecRef _obj(PySequence_GetItem(%PYARG_1, i));
+    cppItems[i] = %CONVERTTOCPP[int](_obj);
+}
+%RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(numItems, cppItems);
+%PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+// @snippet callArrayMethod
+
+// @snippet applyHomogeneousTransform
+bool ok{};
+%RETURN_TYPE retval = %FUNCTION_NAME(%1, %2, %3, %4, %5, %6, %7, %8, %9, %10, &ok);
+if (ok)
+    %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](retval);
+else
+    %PYARG_0 = Py_None;
+// @snippet applyHomogeneousTransform
+
+// @snippet test-argc-argv
+int argc;
+char **argv;
+if (!Shiboken::listToArgcArgv(%PYARG_1, &argc, &argv)) {
+    PyErr_SetString(PyExc_TypeError, "error");
+    return 0;
+}
+%RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(argc, argv);
+%PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+Shiboken::deleteArgv(argc, argv);
+// @snippet test-argc-argv
+
+// @snippet sum2d
+using Inner = std::list<int>;
+
+int result = 0;
+for (const Inner &inner : %1)
+    result += std::accumulate(inner.cbegin(), inner.cend(), 0);
+
+%PYARG_0 = %CONVERTTOPYTHON[int](result);
+// @snippet sum2d
+
+// @snippet sumproduct
+using Pair = std::pair<int, int>;
+
+int result = 0;
+for (const Pair &p : %1)
+    result += p.first * p.second;
+%PYARG_0 = %CONVERTTOPYTHON[int](result);
+// @snippet sumproduct
+
+// @snippet time-comparison
+static bool compareTime(const Time &t, PyObject *rhs, bool defaultValue)
+{
+    if (!PyDateTimeAPI)
+        PyDateTime_IMPORT;
+    if (PyTime_Check(rhs) == 0)
+        return defaultValue;
+    const int pyH = PyDateTime_TIME_GET_HOUR(rhs);
+    const int pyM = PyDateTime_TIME_GET_MINUTE(rhs);
+    const int pyS = PyDateTime_TIME_GET_SECOND(rhs);
+    return pyH == t.hour() && pyM == t.minute() && pyS == t.second();
+}
+// @snippet time-comparison
+
+// @snippet point-str
+const int x1 = int(%CPPSELF.x());
+const int x2 = int((%CPPSELF.x() * 100) - (x1 * 100));
+const int y1 = int(%CPPSELF.y());
+const int y2 = int((%CPPSELF.y() * 100) - (y1 * 100));
+%PYARG_0 = Shiboken::String::fromFormat("%TYPE(%d.%d, %d.%d)", x1, x2, y1, y2);
+// @snippet point-str
+
+// @snippet point-repr
+const int x1 = int(%CPPSELF.x());
+const int x2 = int((%CPPSELF.x() * 10) - (x1 * 10));
+const int y1 = int(%CPPSELF.y());
+const int y2 = int((%CPPSELF.y() * 10) - (y1 * 10));
+%PYARG_0 = Shiboken::String::fromFormat("<%TYPE object at %p: (%d.%d, %d.%d)>",
+                                         %CPPSELF, x1, x2, y1, y2);
+// @snippet point-repr
+
+// @snippet point-reduce
+PyObject *type = PyObject_Type(%PYSELF);
+PyObject *args = Py_BuildValue("(dd)", %CPPSELF.x(), %CPPSELF.y());
+%PYARG_0 = Py_BuildValue("(OO)", type, args);
+// @snippet point-reduce
index 481d06ff682809f481c823870949993a0cd2a2e2..3f1b2e96f201c404468c95175141cbbb491ba877 100644 (file)
@@ -1,5 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <typesystem package="sample">
+
+     <inject-code class="native" position="beginning"
+                  file="samplesnippets.cpp" snippet="module-helpers"/>
+
     <primitive-type name="ObjectType::Identifier"/>
     <primitive-type name="std::nullptr_t"/>
 
         </conversion-rule>
     </primitive-type>
 
-    <inject-code class="native" position="beginning">
-    static bool Check2TupleOfNumbers(PyObject* pyIn) {
-        if (!PySequence_Check(pyIn) || !(PySequence_Size(pyIn) == 2))
-            return false;
-        Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0));
-        if (!PyNumber_Check(pyReal))
-            return false;
-        Shiboken::AutoDecRef pyImag(PySequence_GetItem(pyIn, 1));
-        if (!PyNumber_Check(pyImag))
-            return false;
-        return true;
-    }
-    </inject-code>
     <primitive-type name="Complex" target-lang-api-name="PyComplex">
         <include file-name="complex.h" location="global"/>
         <conversion-rule>
         </conversion-rule>
     </primitive-type>
 
-    <inject-code class="native" position="beginning">
-        static bool checkPyCapsuleOrPyCObject(PyObject* pyObj)
-        {
-            return PyCapsule_CheckExact(pyObj);
-        }
-    </inject-code>
-
     <primitive-type name="PrimitiveStructPtr">
         <include file-name="handle.h" location="local"/>
         <conversion-rule>
         </conversion-rule>
     </primitive-type>
 
+    <value-type name="MoveOnly"/>
+    <object-type name="MoveOnlyHandler"/>
+
     <!-- As of Qt 6, there is a trend of hiding bool returns of comparison
          operators of container classes behind some template expression using
          SFINAE depending on their value's traits, like:
             Py_ssize_t idx = 0;
             for (const auto &amp;s : %in) {
                 PStr cppItem(s);
-                PyList_SET_ITEM(%out, idx++, %CONVERTTOPYTHON[PStr](cppItem));
+                PyList_SetItem(%out, idx++, %CONVERTTOPYTHON[PStr](cppItem));
             }
             return %out;
             </native-to-target>
     </add-function>
 
     <add-function signature="getConversionTypeString(const char*)" return-type="PyObject">
-        <inject-code class="target" position="beginning">
-        Shiboken::Conversions::SpecificConverter converter(%1);
-        const char* %0 = 0;
-        switch (converter.conversionType()) {
-        case Shiboken::Conversions::SpecificConverter::CopyConversion:
-            %0 = "Copy conversion";
-            break;
-        case Shiboken::Conversions::SpecificConverter::PointerConversion:
-            %0 = "Pointer conversion";
-            break;
-        case Shiboken::Conversions::SpecificConverter::ReferenceConversion:
-            %0 = "Reference conversion";
-            break;
-        default:
-            %0 = "Invalid conversion";
-        }
-        %PYARG_0 = %CONVERTTOPYTHON[const char*](%0);
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="samplesnippets.cpp" snippet="getConversionTypeString"/>
     </add-function>
 
-    <inject-code class="native" position="beginning">
-    static PyObject* __convertCppValuesToPython(const char** typeName, void** values, int size)
-    {
-        PyObject* result = PyTuple_New(size);
-        for (int i = 0; i &lt; size; ++i) {
-            Shiboken::Conversions::SpecificConverter converter(typeName[i]);
-            PyTuple_SetItem(result, i, converter.toPython(values[i]));
-        }
-        return result;
-    }
-    </inject-code>
     <add-function signature="convertValueTypeToCppAndThenToPython(Point,Point*,Point&amp;)" return-type="PyObject">
-        <inject-code class="target" position="beginning">
-        const char* typeNames[] = { "Point", "Point*", "Point&amp;" };
-        void* values[] = { &amp;%1, &amp;%2, &amp;(%3) };
-        %PYARG_0 = __convertCppValuesToPython(typeNames, values, 3);
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="samplesnippets.cpp" snippet="convertValueTypeToCppAndThenToPython"/>
     </add-function>
     <add-function signature="convertObjectTypeToCppAndThenToPython(ObjectType*,ObjectType&amp;)" return-type="PyObject">
-        <inject-code class="target" position="beginning">
-        const char* typeNames[] = { "ObjectType*", "ObjectType&amp;" };
-        void* values[] = { &amp;%1, &amp;(%2) };
-        %PYARG_0 = __convertCppValuesToPython(typeNames, values, 2);
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="samplesnippets.cpp" snippet="convertObjectTypeToCppAndThenToPython"/>
     </add-function>
     <add-function signature="convertListOfIntegersToCppAndThenToPython(std::list&lt;int&gt;)" return-type="PyObject">
-        <inject-code class="target" position="beginning">
-        const char* typeNames[] = { "std::list&lt;int&gt;" };
-        void* values[] = { &amp;%1 };
-        %PYARG_0 = __convertCppValuesToPython(typeNames, values, 1);
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="samplesnippets.cpp" snippet="convertListOfIntegersToCppAndThenToPython"/>
     </add-function>
     <add-function signature="convertIntegersToCppAndThenToPython(int,int)" return-type="PyObject">
-        <inject-code class="target" position="beginning">
-        const char* typeNames[] = { "int", "int" };
-        void* values[] = { &amp;%1, &amp;%2 };
-        %PYARG_0 = __convertCppValuesToPython(typeNames, values, 2);
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="samplesnippets.cpp" snippet="convertIntegersToCppAndThenToPython"/>
     </add-function>
 
     <template name="cpp_indexed_list_to_pylist_conversion">
     Py_ssize_t idx = 0;
     for (auto it = %in.cbegin(), end = %in.cend(); it != end; ++it, ++idx) {
         %INTYPE_0 cppItem(*it);
-        PyList_SET_ITEM(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
+        PyList_SetItem(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
     }
     return %out;
     </template>
             </target-to-native>
         </conversion-rule>
     </container-type>
-    <add-function signature="cacheSize()" return-type="int">
+    <add-function signature="wrapperCount()" return-type="int">
         <inject-code class="target">
         %RETURN_TYPE %0 = Shiboken::BindingManager::instance().getAllPyObjects().size();
         %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
     <object-type name="Abstract">
         <enum-type name="Type"/>
         <enum-type name="PrintFormat"/>
-        <modify-function signature="id()" rename="id_"/>
+        <modify-function signature="id()const" rename="id_"/>
         <modify-function signature="hideFunction(HideType*)" remove="all"/>
         <modify-field name="toBeRenamedField" rename="renamedField"/>
         <modify-field name="readOnlyField" write="false"/>
                 <parent index="1" action="add"/>
             </modify-argument>
         </modify-function>
-        <inject-code class="native" position="beginning">
-        static void reparent_layout_items(PyObject* parent, PyObject* layout)
-        {
-            // CHECKTYPE and ISCONVERTIBLE are used here for test purposes, don't change them.
-            if (!%CHECKTYPE[ObjectTypeLayout*](layout) &amp;&amp; !%ISCONVERTIBLE[ObjectTypeLayout*](layout))
-                return;
-            /* %CHECKTYPE[ObjectTypeLayout*](layout) */
-            /* %ISCONVERTIBLE[ObjectTypeLayout*](layout) */
-            ObjectTypeLayout* var;
-            var = %CONVERTTOCPP[ObjectTypeLayout*](layout);
-            // TODO-CONVERTER: erase this
-            /*
-            ObjectTypeLayout* var2 = %CONVERTTOCPP[ObjectTypeLayout*](layout);
-            */
-            const ObjectTypeList&amp; objChildren = var->objects();
-            ObjectTypeList::const_iterator it = objChildren.begin();
-            for (; it != objChildren.end(); ++it) {
-                if ((*it)->isLayoutType()) {
-                    ObjectTypeLayout* l = reinterpret_cast&lt;ObjectTypeLayout*>(*it);
-                    reparent_layout_items(parent, %CONVERTTOPYTHON[ObjectTypeLayout*](l));
-                    Shiboken::Object::setParent(layout, %CONVERTTOPYTHON[ObjectTypeLayout*](l));
-                } else {
-                    Shiboken::Object::setParent(parent, %CONVERTTOPYTHON[ObjectType*](*it));
-                }
-            }
-        }
-        </inject-code>
+        <inject-code class="native" position="beginning"
+                     file="samplesnippets.cpp" snippet="reparent-layout-items"/>
         <modify-function signature="setLayout(ObjectTypeLayout*)">
             <modify-argument index="1">
                 <parent index="this" action="add"/>
             <modify-argument index="2">
                 <replace-type modified-type="PySequence" />
                 <conversion-rule class="native">
-                Shiboken::AutoArrayPointer&lt;Point&gt; %out(%1);
+                Shiboken::ArrayPointer&lt;Point&gt; %out(%1);
                 for (Py_ssize_t i = 0; i &lt; %1; ++i) {
                     Shiboken::AutoDecRef _obj(PySequence_GetItem(%PYARG_1, i));
                     %out[i] = %CONVERTTOCPP[Point](_obj);
           <modify-argument index="return">
             <replace-type modified-type="(status, object)"/>
           </modify-argument>
-          <inject-code class="target" position="beginning">
-          ObjectType* tmpObject = 0;
-          %BEGIN_ALLOW_THREADS
-          %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(&amp;tmpObject);
-          %END_ALLOW_THREADS
-          %PYARG_0 = PyTuple_New(2);
-          PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](%0));
-          PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[ObjectType*](tmpObject));
-          </inject-code>
+          <inject-code class="target" position="beginning"
+                       file="samplesnippets.cpp" snippet="nonConversionRuleForArgumentWithDefaultValue"/>
         </modify-function>
         <modify-function signature="passOddBool(OddBool)" rename="invertBoolean">
           <inject-code class="target" position="beginning">
         </modify-function>
         <modify-function signature="callMe()">
             <inject-code class="native" position="end">
-                PyObject_Call(%PYTHON_METHOD_OVERRIDE, %PYTHON_ARGUMENTS, NULL);
+                PyObject_Call(%PYTHON_METHOD_OVERRIDE, %PYTHON_ARGUMENTS, nullptr);
             </inject-code>
         </modify-function>
         <modify-function signature="createStr(const char*, Str*&amp;)">
                 </conversion-rule>
             </modify-argument>
             <inject-code class="target" position="beginning">
-                Str* _str_arg_ = 0;
-                %RETURN_TYPE %0 = %CPPSELF.%TYPE::%FUNCTION_NAME(%1, _str_arg_);
+                Str *strArg = nullptr;
+                %RETURN_TYPE %0 = %CPPSELF.%TYPE::%FUNCTION_NAME(%1, strArg);
             </inject-code>
             <inject-code class="target" position="end">
                 %PYARG_0 = PyTuple_New(2);
                 PyObject* _item_ = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
                 PyTuple_SetItem(%PYARG_0, 0, _item_);
-                _item_ = %CONVERTTOPYTHON[Str*](_str_arg_);
+                _item_ = %CONVERTTOPYTHON[Str*](strArg);
                 PyTuple_SetItem(%PYARG_0, 1, _item_);
             </inject-code>
         </modify-function>
                 PyTuple_SetItem(%PYARG_0, 1, _item_);
             </inject-code>
         </modify-function>
-        <template name="fix_int*,int*,int*,int*">
-        int a0, a1, a2, a3;
-        %BEGIN_ALLOW_THREADS
-        %CPPSELF->::%TYPE::%FUNCTION_NAME(&amp;a0, &amp;a1, &amp;a2, &amp;a3);
-        %END_ALLOW_THREADS
-        %PYARG_0 = PyTuple_New(4);
-        PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[int](a0));
-        PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[int](a1));
-        PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[int](a2));
-        PyTuple_SetItem(%PYARG_0, 3, %CONVERTTOPYTHON[int](a3));
-        </template>
-        <template name="fix_native_return_int*,int*,int*,int*">
-            PyObject* _obj = %PYARG_0;
-            Shiboken::AutoDecRef _obj0(PySequence_GetItem(_obj, 0));
-            Shiboken::AutoDecRef _obj1(PySequence_GetItem(_obj, 1));
-            Shiboken::AutoDecRef _obj2(PySequence_GetItem(_obj, 2));
-            Shiboken::AutoDecRef _obj3(PySequence_GetItem(_obj, 3));
-            if (!PySequence_Check(_obj)
-                || PySequence_Size(_obj) != 4
-                || !PyNumber_Check(_obj0)
-                || !PyNumber_Check(_obj1)
-                || !PyNumber_Check(_obj2)
-                || !PyNumber_Check(_obj3)) {
-                PyErr_SetString(PyExc_TypeError, "Sequence of 4 numbers expected");
-            } else {
-                *%1 = %CONVERTTOCPP[int](_obj0);
-                *%2 = %CONVERTTOCPP[int](_obj1);
-                *%3 = %CONVERTTOCPP[int](_obj2);
-                *%4 = %CONVERTTOCPP[int](_obj3);
-            }
-        </template>
         <modify-function signature="getMargins(int*,int*,int*,int*)const">
             <modify-argument index="return" pyi-type="Tuple[int, int, int, int]">
                 <replace-type modified-type="PyObject" />
                 <remove-argument/>
                 <remove-default-expression/>
             </modify-argument>
-            <inject-code class="target" position="beginning">
-                <insert-template name="fix_int*,int*,int*,int*"/>
-            </inject-code>
-            <inject-code class="native" position="end">
-                <insert-template name="fix_native_return_int*,int*,int*,int*"/>
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="fix-margins-parameters"/>
+            <inject-code class="native" position="end"
+                         file="samplesnippets.cpp" snippet="fix-margins-return"/>
         </modify-function>
         <modify-function signature="callGetMargins(int*,int*,int*,int*)const">
             <modify-argument index="0">
                 <remove-argument/>
                 <remove-default-expression/>
             </modify-argument>
-            <inject-code class="target" position="beginning">
-                <insert-template name="fix_int*,int*,int*,int*"/>
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="fix-margins-parameters"/>
         </modify-function>
         <modify-function signature="recursionOnModifiedVirtual(Str)const">
             <inject-code class="target" position="beginning">
             <modify-argument index="11">
                 <remove-argument/>
             </modify-argument>
-            <inject-code class="target" position="beginning">
-                bool ok_;
-                %RETURN_TYPE retval_ =
-                    %FUNCTION_NAME(%1, %2, %3, %4, %5, %6, %7, %8, %9, %10, &amp;ok_);
-                if (!ok_)
-                    %PYARG_0 = Py_None;
-                else
-                    %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](retval_);
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="applyHomogeneousTransform"/>
         </modify-function>
     </function>
 
     <!-- Tests add-function for nested template types -->
     <add-function signature="sum2d(std::list&lt;std::list&lt;int&gt; &gt;)" return-type="int">
-        <inject-code class="target" position="beginning">
-            typedef std::list&lt;int&gt; Inner;
-            typedef std::list&lt;Inner&gt; Outer;
-
-            int result = 0;
-
-            Outer::const_iterator oiter, oend = %1.end();
-            for (oiter = %1.begin(); oiter != oend; ++oiter) {
-                const Inner&amp; inner = *oiter;
-                Inner::const_iterator iiter, iend = inner.end();
-                for (iiter = inner.begin(); iiter != iend; ++iiter)
-                    result += *iiter;
-            }
-
-            %PYARG_0 = %CONVERTTOPYTHON[int](result);
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="samplesnippets.cpp" snippet="sum2d"/>
     </add-function>
 
     <!-- Tests add-function for nested template types -->
     <add-function signature="sumproduct(std::list&lt;std::pair&lt;int, int&gt; &gt;)" return-type="int">
-        <inject-code class="target" position="beginning">
-            typedef std::pair&lt;int, int&gt; Pair;
-            typedef std::list&lt;Pair&gt; List;
-
-            int result = 0;
-
-            List::const_iterator iter, end = %1.end();
-            for (iter = %1.begin(); iter != end; ++iter)
-                result += iter->first * iter->second;
-
-            %PYARG_0 = %CONVERTTOPYTHON[int](result);
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="samplesnippets.cpp" snippet="sumproduct"/>
     </add-function>
 
 
             <modify-argument index="1">
                 <replace-type modified-type="PyObject"/>
             </modify-argument>
-            <inject-code class="target" position="beginning">
-            int* array = NULL;
-            bool errorOccurred = false;
-
-            if (PySequence_Check(%PYARG_1)) {
-                if((array = Shiboken::sequenceToIntArray(%PYARG_1, true)) == NULL &amp;&amp; PyErr_Occurred()) {
-                    PyErr_SetString(PyExc_TypeError, "Should be a sequence of ints");
-                    errorOccurred = true;
-                }
-            } else {
-                PyErr_SetString(PyExc_TypeError, "Should be a sequence of ints");
-                errorOccurred = true;
-            }
-
-            if (!errorOccurred) {
-                %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(array);
-                if (array)
-                    delete[] array;
-                %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
-            }
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="sumArrayAndLength"/>
         </modify-function>
 
         <modify-function signature="arrayMethod(int, int*) const">
                 <replace-type modified-type="PySequence"/>
                 <conversion-rule class="native">
                 const Py_ssize_t numItems = PySequence_Size(%PYARG_1);
-                Shiboken::AutoArrayPointer&lt;int&gt; %out(numItems);
+                Shiboken::ArrayPointer&lt;int&gt; %out(numItems);
                 for (Py_ssize_t i = 0; i &lt; numItems; ++i) {
                     Shiboken::AutoDecRef _obj(PySequence_GetItem(%PYARG_1, i));
                     if (%CHECKTYPE[int](_obj))
                 <conversion-rule class="target">
                 PyObject* %out = PyList_New(count);
                 for (int i = 0; i &lt; count; ++i)
-                    PyList_SET_ITEM(%out, i, %CONVERTTOPYTHON[int](%in[i]));
+                    PyList_SetItem(%out, i, %CONVERTTOPYTHON[int](%in[i]));
                 </conversion-rule>
             </modify-argument>
         </modify-function>
             <modify-argument index="2">
                 <replace-type modified-type="PySequence"/>
             </modify-argument>
-            <inject-code class="target" position="beginning">
-            Py_ssize_t numItems = PySequence_Size(%PYARG_1);
-            int *cppItems = new int[numItems];
-            for (int i = 0; i &lt; numItems; i++) {
-                Shiboken::AutoDecRef _obj(PySequence_GetItem(%PYARG_1, i));
-                cppItems[i] = %CONVERTTOCPP[int](_obj);
-            }
-            %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(numItems, cppItems);
-            %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
-            delete[] cppItems;
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="callArrayMethod"/>
         </modify-function>
 
         <!--
             <modify-argument index="2">
                 <remove-argument />
             </modify-argument>
-            <inject-code class="target" position="beginning">
-            int argc;
-            char** argv;
-            if (!Shiboken::listToArgcArgv(%PYARG_1, &amp;argc, &amp;argv)) {
-                PyErr_SetString(PyExc_TypeError, "error");
-                return 0;
-            }
-            %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(argc, argv);
-            %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
-            for (int i = 0; i &lt; argc; ++i)
-                free(argv[i]);
-            delete[] argv;
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="test-argc-argv"/>
         </modify-function>
 
         <!-- Tested in InjectCodeTest.testArgsModification2 -->
             <modify-argument index="2">
                 <remove-argument />
             </modify-argument>
-            <inject-code class="target" position="beginning">
-            int argc;
-            char** argv;
-            if (!Shiboken::listToArgcArgv(%PYARG_1, &amp;argc, &amp;argv)) {
-                PyErr_SetString(PyExc_TypeError, "error");
-                return 0;
-            }
-            %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(argc, argv);
-            %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
-            for (int i = 0; i &lt; argc; ++i)
-                free(argv[i]);
-            delete[] argv;
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="test-argc-argv"/>
         </modify-function>
     </value-type>
 
             <modify-argument index="1">
                 <replace-type modified-type="PyObject" />
                 <conversion-rule class="native">
-                unsigned char* %out = 0;
+                unsigned char *%out = nullptr;
                 </conversion-rule>
             </modify-argument>
         </modify-function>
 
     <value-type name="Point">
         <add-function signature="__str__" return-type="str">
-            <inject-code class="target" position="beginning">
-            int x1 = (int) %CPPSELF.x();
-            int x2 = ((int) (%CPPSELF.x() * 100)) - (x1 * 100);
-            int y1 = (int) %CPPSELF.y();
-            int y2 = ((int) (%CPPSELF.y() * 100)) - (y1 * 100);
-            %PYARG_0 = Shiboken::String::fromFormat("Point(%d.%d, %d.%d)", x1, x2, y1, y2);
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="point-str"/>
         </add-function>
         <add-function signature="__repr__" return-type="str">
-            <inject-code class="target" position="beginning">
-            int x1 = (int) %CPPSELF.x();
-            int x2 = ((int) (%CPPSELF.x() * 10)) - (x1 * 10);
-            int y1 = (int) %CPPSELF.y();
-            int y2 = ((int) (%CPPSELF.y() * 10)) - (y1 * 10);
-            %PYARG_0 = Shiboken::String::fromFormat("&lt;Point object at %p: (%d.%d, %d.%d)&gt;", %CPPSELF, x1, x2, y1, y2);
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="point-repr"/>
         </add-function>
 
         <add-function signature="__reduce__" return-type="str">
-            <inject-code class="target" position="beginning">
-            PyObject* type = PyObject_Type(%PYSELF);
-            PyObject* args = NULL;
-
-            args = Py_BuildValue("(dd)", %CPPSELF.x(), %CPPSELF.y());
-
-            %PYARG_0 = Py_BuildValue("(OO)", type, args);
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="point-reduce"/>
         </add-function>
 
         <modify-function signature="midpoint(const Point&amp;, Point*)const">
 
     <value-type name="PointF">
         <add-function signature="__str__" return-type="str">
-            <inject-code class="target" position="beginning">
-            int x1 = (int) %CPPSELF.x();
-            int x2 = ((int) (%CPPSELF.x() * 100)) - (x1 * 100);
-            int y1 = (int) %CPPSELF.y();
-            int y2 = ((int) (%CPPSELF.y() * 100)) - (y1 * 100);
-            %PYARG_0 = Shiboken::String::fromFormat("PointF(%d.%d, %d.%d)", x1, x2, y1, y2);
-            </inject-code>
+                      file="samplesnippets.cpp" snippet="point-str"/>
         </add-function>
         <add-function signature="__repr__" return-type="str">
-            <inject-code class="target" position="beginning">
-            int x1 = (int) %CPPSELF.x();
-            int x2 = ((int) (%CPPSELF.x() * 10)) - (x1 * 10);
-            int y1 = (int) %CPPSELF.y();
-            int y2 = ((int) (%CPPSELF.y() * 10)) - (y1 * 10);
-            %PYARG_0 = Shiboken::String::fromFormat("&lt;PointF object at %p: (%d.%d, %d.%d)&gt;", %CPPSELF, x1, x2, y1, y2);
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="point-repr"/>
         </add-function>
 
         <add-function signature="__reduce__" return-type="str">
-            <inject-code class="target" position="beginning">
-            PyObject *type = PyObject_Type(%PYSELF);
-            PyObject *args = NULL;
-
-            args = Py_BuildValue("(dd)", %CPPSELF.x(), %CPPSELF.y());
-
-            %PYARG_0 = Py_BuildValue("(OO)", type, args);
-            </inject-code>
+            <inject-code class="target" position="beginning"
+                         file="samplesnippets.cpp" snippet="point-reduce"/>
         </add-function>
 
         <modify-function signature="midpoint(const PointF&amp;, PointF*)const">
         <extra-includes>
           <include file-name="datetime.h" location="global"/>
         </extra-includes>
+        <inject-code class="native" position="beginning"
+                     file="samplesnippets.cpp"  snippet="time-comparison"/>
         <enum-type name="NumArgs"/>
         <add-function signature="operator!=(const PyObject*)" return-type="PyObject">
           <inject-code>
-            if (!PyDateTimeAPI)
-                PyDateTime_IMPORT;
-            if (PyTime_Check(%1)) {
-                int pyH = PyDateTime_TIME_GET_HOUR(%1);
-                int pyM = PyDateTime_TIME_GET_MINUTE(%1);
-                int pyS = PyDateTime_TIME_GET_SECOND(%1);
-                if ((pyH == %CPPSELF.hour()) &amp;&amp;
-                    (pyM == %CPPSELF.minute()) &amp;&amp;
-                    (pyS == %CPPSELF.second()))
-                    %PYARG_0 = Py_False;
-                else
-                    %PYARG_0 = Py_True;
-                Py_INCREF(%PYARG_0);
-            }
+          %PYARG_0 = compareTime(%CPPSELF, %1, true) ? Py_False : Py_True;
+          Py_INCREF(%PYARG_0);
           </inject-code>
         </add-function>
         <add-function signature="operator==(const PyObject*)" return-type="PyObject">
           <inject-code>
-            if (!PyDateTimeAPI)
-                PyDateTime_IMPORT;
-            if (PyTime_Check(%1)) {
-                int pyH = PyDateTime_TIME_GET_HOUR(%1);
-                int pyM = PyDateTime_TIME_GET_MINUTE(%1);
-                int pyS = PyDateTime_TIME_GET_SECOND(%1);
-                if ((pyH == %CPPSELF.hour()) &amp;&amp;
-                    (pyM == %CPPSELF.minute()) &amp;&amp;
-                    (pyS == %CPPSELF.second()))
-                    %PYARG_0 = Py_True;
-                else
-                    %PYARG_0 = Py_False;
-                Py_INCREF(%PYARG_0);
-            }
+          %PYARG_0 = compareTime(%CPPSELF, %1, false) ? Py_True : Py_False;
+          Py_INCREF(%PYARG_0);
           </inject-code>
         </add-function>
 
                 {
                     Shiboken::AutoDecRef strList(PySequence_Fast(%PYARG_1, "The argument must be a sequence."));
                     const Py_ssize_t lineCount = PySequence_Size(strList.object());
-                    for (int line = 0; line &lt; lineCount; ++line) {
+                    for (Py_ssize_t line = 0; line &lt; lineCount; ++line) {
                         Shiboken::AutoDecRef _obj(PySequence_GetItem(strList.object(), line));
                         bool isString = Shiboken::String::check(_obj);
                         if (!isString) {
                 // PySIDE-1735: Enums are now implemented in Python, so we need to avoid asserts.
                 if (PyErr_Occurred())
                     break;
-                const char** %out = 0;
+                const char **%out = nullptr;
                 </conversion-rule>
             </modify-argument>
         </modify-function>
             <modify-argument index="1">
                 <replace-type modified-type="PyObject" />
                 <conversion-rule class="native">
-                void* %out = 0;
+                void *%out = nullptr;
                 </conversion-rule>
             </modify-argument>
         </modify-function>
                 <replace-type modified-type="PyBytes"/>
             </modify-argument>
             <inject-code class="target" position="beginning">
-            PyObject* data = 0;
+            PyObject *data = nullptr;
             if (PyUnicode_CheckExact(%PYARG_1)) {
                 data = PyUnicode_AsASCIIString(%PYARG_1);
             } else {
index cf79bc6ba913bca3c68ddcd9f23c1ba04c8a39f0..14b181b6134385747a0ac268143dd66c80993926 100644 (file)
@@ -2,22 +2,6 @@
 <typesystem package="smart">
     <rejection class="*" argument-type="^std::nullptr_t&amp;?$"/>
 
-    <template name="cpplist_to_pylist_convertion">
-        PyObject *%out = PyList_New(int(%in.size()));
-        int idx  = 0;
-        for (const auto &amp;cppItem : %in)
-            PyList_SET_ITEM(%out, idx++, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
-        return %out;
-    </template>
-    <template name="pyseq_to_cpplist_convertion">
-        Shiboken::AutoDecRef seq(PySequence_Fast(%in, 0));
-        for (Py_ssize_t i = 0, size = PySequence_Size(seq.object()); i &lt; size; ++i) {
-            Shiboken::AutoDecRef pyItem(PySequence_GetItem(seq.object(), i));
-            %OUTTYPE_0 cppItem = %CONVERTTOCPP[%OUTTYPE_0](pyItem);
-            %out.push_back(cppItem);
-        }
-    </template>
-
     <!-- Used in tests to check what C++ objects are allocated. -->
     <object-type name="Registry" />
 
index f2f6bd3e3ffa93782e3c9abecea031c9cb98f225..94902d94cead61b671044dca278560b5e512057d 100644 (file)
@@ -5,9 +5,10 @@
 #include "dummygenerator.h"
 #include "dummygentestconfig.h"
 
-#include <QtCore/QProcess>
-#include <QtCore/QTemporaryFile>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
+
+#include <QtCore/qprocess.h>
+#include <QtCore/qtemporaryfile.h>
 
 #define GENERATED_CONTENTS  "// Generated code for class: Dummy"
 
index be4ed8a07052850ade335f70875c921bfaf6a7ae..9ef35ee207f9556c809d52a766327d3e88b49b32 100644 (file)
@@ -39,6 +39,7 @@ import argparse
 import os
 import sys
 from collections import OrderedDict
+from pathlib import Path
 from textwrap import dedent
 from timeit import default_timer as timer
 
@@ -117,9 +118,13 @@ def test_project(project, args, blacklist, runs):
             # PYSIDE-1229: When a fatal error happens, bail out immediately!
             if item.fatal:
                 fatal = item
-        print()
+
+        print("\n  #### Top 20 slow tests:")
+        for item in results.get_slowest_tests(20):
+            print(f"    {item.mod_name:<50} {item.time:6}s")
+
         print(
-            f"Totals: {sum(r)} tests. "
+            f"\nTotals: {sum(r)} tests. "
             f"{r[0]} passed, {r[1]} failed, {r[2]} skipped, {r[3]} blacklisted, {r[4]} bpassed."
         )
         print()
@@ -163,7 +168,7 @@ def main():
     group.add_argument(
         "--blacklist",
         "-b",
-        type=argparse.FileType("r"),
+        type=str,
         default=blacklist_default,
         help=f"a Qt blacklist file (default: {blacklist_default})",
     )
@@ -191,7 +196,7 @@ def main():
     )
     parser_getcwd = subparsers.add_parser("getcwd")
     parser_getcwd.add_argument(
-        "filename", type=argparse.FileType("w"), help="write the build dir name into a file"
+        "filename", type=str, help="write the build dir name into a file"
     )
     parser_getcwd.add_argument(
         "--buildno",
@@ -209,8 +214,8 @@ def main():
             sys.exit(1)
 
     if args.subparser_name == "getcwd":
-        print(builds.selected.build_dir, file=args.filename)
-        print(builds.selected.build_dir, "written to file", args.filename.name)
+        Path(args.filename).write_text(builds.selected.build_dir + '\n')
+        print(builds.selected.build_dir, "written to file", args.filename)
         sys.exit(0)
     elif args.subparser_name == "test":
         runs = args.reruns
@@ -231,8 +236,7 @@ def main():
         sys.exit(1)
 
     if args.blacklist:
-        args.blacklist.close()
-        bl = BlackList(args.blacklist.name)
+        bl = BlackList(args.blacklist)
     else:
         bl = BlackList(None)
     if args.environ:
index abf00ef3ff41bbee9532a4f67e9325cc122fe3a1..38be8d1eda327c38fdf08f7ce2609f53c969e171 100644 (file)
@@ -4,7 +4,7 @@ from __future__ import annotations
 
 import os
 import re
-from collections import namedtuple
+from dataclasses import dataclass
 from io import StringIO
 
 """
@@ -70,9 +70,22 @@ assert re.match(_TEST_PAT, _EXAMPLE.splitlines()[5], re.VERBOSE)
 assert len(re.match(_TEST_PAT, _EXAMPLE.splitlines()[5], re.VERBOSE).groups()) == 8
 assert len(re.match(_TEST_PAT, _EXAMPLE.splitlines()[7], re.VERBOSE).groups()) == 8
 
-TestResult = namedtuple(
-    "TestResult", "idx n sharp mod_name passed " "code time fatal rich_result".split()
-)
+
+@dataclass
+class TestResult:
+    idx: int = 0
+    n: int = 0
+    sharp: int = 0
+    mod_name: str = ""
+    passed: bool = False
+    code: str = ""
+    time: float = 0
+    fatal: bool = False
+    rich_result: str = ""
+
+
+def sort_time_key(item):
+    return item.time
 
 
 def _parse_tests(test_log):
@@ -114,9 +127,9 @@ def _parse_tests(test_log):
         if idx + 1 != item.idx:
             # The numbering is disrupted. Provoke an error in this line!
             code = f"{code}, but lines are disrupted!"
-            result[idx] = item._replace(
-                passed=False, code=f"{item.code}, but lines are disrupted!", fatal=True
-            )
+            result[idx].passed = False
+            result[idx].code = f"{item.code}, but lines are disrupted!"
+            result[idx].fatal = True
             break
     return result
 
@@ -150,4 +163,12 @@ class TestParser:
             if item.fatal:
                 # PYSIDE-1229: Stop the testing completely when a fatal error exists
                 res = "FATAL"
-            yield item._replace(rich_result=res)
+            item.rich_result = res
+            yield item
+
+    def get_slowest_tests(self, max_count):
+        result = self.results.copy()
+        result.sort(key=sort_time_key, reverse=True)
+        if len(result) > max_count:
+            result = result[0:max_count - 1]
+        return result
index ad1e01d6534a3f8ed548e1f705ba6a338adf8492..4d96fdfc219893f98d9c9230ce6f9297124fb630 100644 (file)
@@ -20,7 +20,7 @@ this_dir = os.path.dirname(this_file)
 build_scripts_dir = os.path.abspath(os.path.join(this_dir, ".."))
 
 sys.path.append(build_scripts_dir)
-from build_scripts.utils import detect_clang
+from build_scripts.utils import detect_clang    # noqa: E402
 
 
 class TestRunner:
@@ -78,11 +78,12 @@ class TestRunner:
         Helper for _find_ctest() that finds the ctest binary in a build
         system file (ninja, Makefile).
         """
-        look_for = "--force-new-ctest-process"
+        # Looking for a command ending this way:
+        look_for = "\\ctest.exe" if "win32" in sys.platform else "/ctest"
         line = None
         with open(file_name) as makefile:
             for line in makefile:
-                if look_for in line:
+                if look_for in line and line.lstrip().startswith("COMMAND"):
                     break
             else:
                 # We have probably forgotten to build the tests.
@@ -98,7 +99,8 @@ class TestRunner:
                 raise RuntimeError(msg)
         # the ctest program is on the left to look_for
         assert line, f"Did not find {look_for}"
-        ctest = re.search(r'(\S+|"([^"]+)")\s+' + look_for, line).groups()
+        look = re.escape(look_for)
+        ctest = re.search(fr'(\S+{look}|"([^"]+{look})")', line).groups()
         return ctest[1] or ctest[0]
 
     def _find_ctest(self):
index 0f6fb04ffd87d2eeeef24a1aeb409f782445975a..e5b47ffc1b7d214547ef56701e7e71fcd42a1907 100644 (file)
@@ -84,10 +84,10 @@ def package_prefix_names():
     # Note: shiboken6_generator is not needed for compile_using_nuitka,
     # but building modules with cmake needs it.
     if NEW_WHEELS:
-        return ["shiboken6", "shiboken6_generator", "PySide6_Essentials", "PySide6_Addons",
-                "PySide6"]
+        return ["shiboken6", "shiboken6_generator", "pyside6_essentials", "pyside6_addons",
+                "pyside6"]
     else:
-        return ["shiboken6", "shiboken6_generator", "PySide6"]
+        return ["shiboken6", "shiboken6_generator", "pyside6"]
 
 
 def clean_egg_info():
index 45dd874f5a7c43e95a3979ad3b57ea5a818da608..d70e38ea2ef1bfca089ac9ea43d04c011bf908dd 100644 (file)
@@ -17,8 +17,8 @@ from tqdm import tqdm
 
 # the tag number does not matter much since we update the sdk later
 DEFAULT_SDK_TAG = 6514223
-ANDROID_NDK_VERSION = "26b"
-ANDROID_NDK_VERSION_NUMBER_SUFFIX = "10909125"
+ANDROID_NDK_VERSION = "27c"
+ANDROID_NDK_VERSION_NUMBER_SUFFIX = "12479018"
 
 
 def run_command(command: list[str], cwd: str | None = None, ignore_fail: bool = False,
@@ -163,16 +163,9 @@ def download_android_ndk(ndk_path: Path):
 
             print("Unpacking Android Ndk")
             if sys.platform == "darwin":
-                extract_dmg(file=(ndk_path
-                            / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"),
-                            destination=ndk_path)
-                ndk_version_path = (ndk_version_path
-                                    / (f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app"
-                                       "/Contents/NDK"))
+                extract_dmg(file=ndk_zip_path, destination=ndk_path)
             else:
-                extract_zip(file=(ndk_path
-                            / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"),
-                            destination=ndk_path)
+                extract_zip(file=ndk_zip_path, destination=ndk_path)
         except Exception as e:
             print(f"Error occurred while downloading and unpacking Android NDK: {e}")
             if ndk_path.exists():
index 2884793caced8948251acf8a92852f0c8c458433..6636d080f9fd5285ce67f1c948404866d1f817c2 100644 (file)
@@ -104,7 +104,7 @@ if __name__ == "__main__":
 
     parser.add_argument("-v", "--verbose", help="run in verbose mode", action="store_const",
                         dest="loglevel", const=logging.INFO)
-    parser.add_argument("--api-level", type=str, default="34",
+    parser.add_argument("--api-level", type=str, default="35",
                         help="Minimum Android API level to use")
     parser.add_argument("--ndk-path", type=str, help="Path to Android NDK (Preferred r26b)")
     # sdk path is needed to compile all the Qt Java Acitivity files into Qt6AndroidBindings.jar
index c1cf6686f2e5197a62af8bded2c9a761416c967c..0d630999da49e426c48f3a1d51fb9dda98560ec1 100644 (file)
@@ -28,11 +28,15 @@ set(QT_COMPILER_FLAGS "--target={{ plat_name }}-linux-android${_TARGET_NAME_ENDI
                        -fomit-frame-pointer \
                        -march={{ gcc_march }} \
                        -msse4.2 \
-                       -mpopcnt \
                        -m{{ plat_bits }} \
                        -fPIC \
                        -I{{ target_python_path }}/include/python{{ python_version }} \
                        -Wno-unused-command-line-argument")
+
+if (NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
+    set(QT_COMPILER_FLAGS "${QT_COMPILER_FLAGS} -mpopcnt")
+endif()
+
 set(QT_COMPILER_FLAGS_RELEASE "-O2 -pipe")
 
 # FIXME
index 3977bd69ee038d9423ad428d6bdef4290e155650..a26811a9033e3a00309a4a0b29221d9744e9b993 100644 (file)
@@ -2,7 +2,6 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 from __future__ import annotations
 
-import os
 import subprocess
 import sys
 from argparse import ArgumentParser, RawTextHelpFormatter
@@ -190,8 +189,8 @@ if __name__ == "__main__":
     module_dependency_dict = {}
     for m in SOURCE_DIR.glob("Qt*"):
         module = m.name
-        # QtGraphs duplicates symbols from QtDataVisualization causing shiboken errors
-        if module == "QtDataVisualization":
+        # QtGraphs duplicates symbols from QtDataVisualization/QtCharts causing shiboken errors
+        if module == "QtDataVisualization" or module == "QtCharts":
             continue
         qt_include_path = qt_include_dir / module
         if qt_include_path.is_dir():
index 1b66b566805013787711faa88c10c04a65868068..8dc0789fa292cf86deab2eeb1244079e5f338f00 100644 (file)
@@ -271,7 +271,7 @@ def get_module_gallery(examples):
                 print(f"example_gallery: No .rst or .md file found in {original_dir}")
                 continue
 
-            with original_file.open("r") as f:
+            with original_file.open("r", encoding="utf-8") as f:
                 # Read the first line
                 first_line = f.readline().strip()
 
index e5880a09b06da84b63501ec656ca4b3411bfa6fd..29e892abb96301b4cd291d0303c3d08a1321d11e 100644 (file)
@@ -13,6 +13,7 @@ from handlers import (handle_array_declarations, handle_casts, handle_class,
                       handle_type_var_declaration, handle_useless_qt_classes,
                       handle_new,
                       handle_void_functions, handle_qt_connects)
+from enum_migration import qualify_enums
 from parse_utils import dstrip, get_indent, remove_ref
 
 
@@ -86,6 +87,8 @@ def snippet_translate(x):
     if qt_connects:
         return qt_connects
 
+    x = qualify_enums(x)
+
     # Handle "->"
     if "->" in x:
         x = x.replace("->", ".")
diff --git a/tools/snippets_translate/enum_migration.py b/tools/snippets_translate/enum_migration.py
new file mode 100644 (file)
index 0000000..6db7aad
--- /dev/null
@@ -0,0 +1,69 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+MOUSE_BUTTONS = ("NoButton", "AllButtons", "LeftButton", "RightButton", "MiddleButton",
+                 "BackButton", "XButton", "ExtraButton", "ForwardButton", "ExtraButton",
+                 "TaskButton")
+
+MODIFIERS = ("NoModifier", "ShiftModifier", "ControlModifier", "AltModifier",
+             "MetaModifier", "KeypadModifier", "GroupSwitchModifier")
+
+GLOBAL_COLORS = ("white", "black", "red", "darkRed", "green", "darkGreen", "blue",
+                 "darkBlue", "cyan", "darkCyan", "magenta", "darkMagenta", "yellow",
+                 "darkYellow", "gray", "darkGray", "lightGray", "transparent")
+
+ALIGN_VALUES = ("AlignHCenter", "AlignLeft", "AlignCenter", "AlignRight", "AlignVCenter",
+                "AlignTop", "AlignBotton", "AlignJustify", "AlignBaseline", "AlignAbsolute",
+                "AlignLeading", "Trailing")
+
+
+def _get_replacements():
+    result = [
+        ("Qt::Key_", "Qt.Key.Key_"),
+        ("Qt::CTRL", "Qt.Modifier.CTRL"),
+        ("Qt::ALT", "Qt.Modifier.ALT"),
+        ("Qt::CaseInsensitive", "Qt.CaseSensitivity.CaseInsensitive"),
+        ("Qt::CaseSensitive", "Qt.CaseSensitivity.CaseSensitive"),
+        ("QImage::Format_", "QImage.Format.Format_"),
+        ("Qt::WA_DeleteOnClose", "Qt.WidgetAttribute.WA_DeleteOnClose"),
+        ("QQuickView::Ready", "QQuickView.Status.Ready"),
+        ("QQuickView::Error", "QQuickView.Status.Error"),
+        ("QQuickView::Loading", "QQuickView.Status.Loading"),
+        ("QPainter::Antialiasing", "QPainter.RenderHint.Antialiasing"),
+        ("QQuickView::SizeRootObjectToView", "QQuickView.ResizeMode.SizeRootObjectToView"),
+        ("QQuickView::SizeViewToRootObject", "QQuickView.ResizeMode.SizeViewToRootObject"),
+        ("QKeySequence::", "QKeySequence.StandardKey."),
+        ("QEvent::", "QEvent.Type.")
+    ]
+    for c in GLOBAL_COLORS:
+        result.append((f"Qt::{c}", f"Qt.GlobalColor.{c}"))
+    for b in ("Close", "Ok", "Cancel", "Yes", "No"):
+        result.append((f"QDialogButtonBox::{b}", f"QDialogButtonBox.StandardButton.{b}"))
+        result.append((f"QMessageBox::{b}", f"QMessageBox.StandardButton.{b}"))
+    for b in MOUSE_BUTTONS:
+        result.append((f"Qt::{b}", f"Qt.MouseButton.{b}"))
+    for a in ALIGN_VALUES:
+        result.append((f"Qt::{a}", f"Qt.AlignmentFlag.{a}"))
+    for m in MODIFIERS:
+        result.append((f"Qt::{m}", f"Qt.KeyboardModifier.{m}"))
+    for m in ("ReadOnly", "WriteOnly", "Text"):
+        result.append((f"QIODevice::{m}", f"QIODevice.OpenModeFlag.{m}"))
+        result.append((f"QFile::{m}", f"QFile.OpenModeFlag.{m}"))
+    for p in ("Preferred", "Ignored", "Fixed", "Maximum", "Minimum", "Expanding"):
+        result.append((f"QSizePolicy::{p}", f"QSizePolicy.Policy.{p}"))
+    for r in ("DisplayRole", "EditRole"):
+        result.append((f"Qt::{r}", f"Qt.ItemDataRole.{r}"))
+    for f in ("Box", "StyledPanel", "Panel", "WinPanel", "NoFrame"):
+        result.append((f"QFrame::{f}", "QFrame.Shape.{f}"))
+    for f in ("Raised", "Sunken"):
+        result.append((f"QFrame::{f}", "QFrame.Shadow.{f}"))
+    return result
+
+
+REPLACEMENTS = _get_replacements()
+
+
+def qualify_enums(s):
+    for replacement in REPLACEMENTS:
+        s = s.replace(replacement[0], replacement[1])
+    return s
index f660033c11c2f3db07fccca347ad059cd0ed7e6c..34a52352572e1ffb634c4b045d5db8b061ad8ce3 100644 (file)
@@ -1,4 +1,4 @@
 {
-    "files": ["main.py", "converter.py", "handlers.py", "override.py",
+    "files": ["main.py", "converter.py", "handlers.py", "override.py", "enum_migration.py",
               "tests/test_converter.py", "tests/test_snippets.py"]
 }
index beab3ad09292e5f9c7dbe69b84e0a955b8cd158c..112f7a05a74ace30c8665be4d4350ff11d1206ae 100644 (file)
@@ -68,6 +68,10 @@ def test_else():
     assert st("else {") == "else:"
 
 
+def test_qualify_enumerations():
+    assert st("Qt::Key_A") == "Qt.Key.Key_A"
+
+
 def test_new():
     assert st("a = new Something(...);") == "a = Something(...)"
     assert st("a = new Something") == "a = Something()"
index 3e289fd7980c1a4bf50a34447ffcaeed5f15566b..ad9f12c4381542ec21eeb2dce63a7cfaab85287e 100644 (file)
@@ -12,7 +12,7 @@ readme = "PROJECT_README"
 dynamic = ["version"]
 requires-python = ">=3.9, <3.14"
 keywords = ["Qt"]
-license = {text = "LGPL"}
+license = {text="LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"}
 dependencies = "PROJECT_DEPENDENCIES"
 classifiers = [
     "Development Status :: 5 - Production/Stable",
@@ -21,8 +21,6 @@ classifiers = [
     "Environment :: X11 Applications :: Qt",
     "Environment :: Win32 (MS Windows)",
     "Intended Audience :: Developers",
-    "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
-    "License :: Other/Proprietary License",
     "Operating System :: MacOS :: MacOS X",
     "Operating System :: POSIX",
     "Operating System :: POSIX :: Linux",